1

Based on these 2 urls:

I would like:

  • a MasterComponent
  • with a ListComponent
  • and a ListItemComponent

MasterComponent.ts

  public data: Data;
  public form: FormGroup = this.fb.group({});

  formInitialized(name: string, form: FormGroup) {
    this.form.addControl(name, form);
  }

MasterComponent.html

  <app-list [parentForm]="form"
        [items]="data.items"
        (formReady)="formInitialized('items', $event)"></app-list>

ListComponent.ts

  @Input() parentForm: FormGroup;
  @Input() items: item[];
  @Output() formReady = new EventEmitter<AbstractControl>()

  public itemsForm: FormArray;

  formInitialized(itemForm: FormGroup) {
    debugger;
    this.itemsForm.push(itemForm);
  }

  ngOnInit() {
    debugger;
    this.itemsForm = new FormArray([]);
    this.formReady.emit(this.itemsForm);
  }

ListComponent.html

    <div [formGroup]="parentForm">
      <div formArrayName="items">
        <ng-template let-item let-last="last" let-i="index" ngFor [ngForOf]="items">
          <app-item [itemsForm]="parentForm.controls.items"
                         [item]="item"
                         (formReady)="formInitialized($event)">
      </app-item>
    </ng-template>
  </div>
</div>

item.component.ts

  @Input() itemsForm: FormArray;
  @Input() item: Item;
  @Output() formReady = new EventEmitter<AbstractControl>()

  public itemForm: FormGroup;
  ngOnInit() {
    this.itemForm = this._formBuilder.group({title: [item.title || '', Validators.required],});
    this.formReady.emit(this.itemForm);
  }

item.component.html

<div class="md-padding" [formGroup]="itemForm">
 <mat-form-field flex>
        <input #title
               name="title"
               matInput
               type="text"
               placeholder="Title"
               formControlName="title"
               maxlength="150"
               required />
        <mat-error *ngIf="title.pristine || !title.errors">
          <div *ngIf="title.errors?.required">
            this field is required.
          </div>
          <div *ngIf="title.errors?.maxlength">
            max length is 150.
          </div>
        </mat-error>
        <mat-hint align="end">{{title.value.length}} / 150</mat-hint>
      </mat-form-field>
</div>

I have an error with code:

MasterComponent.html:12 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'Items: [object Object],[object Object]core.js:8445)
    at expressionChangedAfterItHasBeenCheckedError (core.js:8433)
    at checkBindingNoChanges (core.js:8535)
    at checkNoChangesNodeInline (core.js:11403)
    at checkNoChangesNode (core.js:11390)
    at debugCheckNoChangesNode (core.js:11997)
    at debugCheckDirectivesFn (core.js:11925)
    at Object.eval [as updateDirectives] (MasterComponent.html:12)
    at Object.debugUpdateDirectives [as updateDirectives] (core.js:11914)
    at checkNoChangesView (core.js:11289)

I like the concept where the child component is in charge to give the formGroup to the master component 1. The problem with the solution 1 is the child component can't update the mastercomponent (see the issue https://github.com/brophdawg11/ng-playground/issues/5). So I adapted the solution 1 with the solution 2.

But it doesnt work. Is there a way to fix the problem? Or do you have a good tutorial to propose?

Thanks

2
  • This looks very suspect to me (formReady)="formInitialized($event)". Looks like your using an @Output() binding to pass internal references to other components, and then the inner state of that component is changed by outsiders. Which explains why you're getting the change error. Commented Jun 6, 2018 at 2:34
  • I want my childcomponent updates the MasterForm by adding a new property. In this way, the childComponent will be more re usuable in other components. so yes, ListComponent formInitialized send the new FormArray to MasterComponent and ItemComponent formInitialized send the new FormGroup to add to the ListComponent FormArray. I m opened to change a lot of thing, my main point is to keep the responsability to the child component to send the AbstractControl to the parent component. Commented Jun 6, 2018 at 2:40

1 Answer 1

3

You can just follow Brophy's example...

Instead of having formReady emit when you create the formControl/formGroup, rather just add it to the parent form by passing the parent in through an @input.

The trick is to update the form at the correct index instead of just pushing to the formArray...

For example,

The Parent: (TS)

styleForm: FormGroup;
@Input() style: IStyle;

constructor(private fb: FormBuilder) {}

ngOnInit(): void {
  this.styleForm = this.buildStyleForm();
}


buildStyleForm() {
  return this.fb.group({
    selected: false,
    products: this.fb.array([])
  });
}

The Parent: (HTML)

<ul class="style_product-list" formArrayName="products">
  <li class="style_product" *ngFor="let product of style?.products; index as i">
    <product [styleForm]="styleForm" [productIndex]="i" [product]="product"></product>
  </li>
</ul>

The Child:

products: FormArray;
@Input() styleForm: FormGroup;
@Input() product: IProduct;
@Input() productIndex: number;

ngOnInit(): void {
  this.productForm = this.buildProductFormGroup(this.product);
  this.products = this.styleForm.controls.products as FormArray;
  this.pushGroupIntoArray(this.products, this.productForm, this.productIndex);
}

pushGroupIntoArray(array: FormArray, group: FormGroup, index: number) {
  if (array.at(index)) {
    array.setControl(index, group);
  } else {
    array.push(group);
  }
}

buildProductFormGroup(product: IProduct): FormGroup {
  return <FormGroup > this.fb.group({
    selected: [product && product.selected],
    quantity: [product && product.quantity, Validators.required],
    parts: [product && product.parts, Validators.required]
  });
}

This will give the updated formControl/formGroup to the parent (and it will be linked!)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.