1
votes

I'm building own form controls. In loop of fields I'm adding FormControls to FormGroup. But when I try to define validators composition it throws me ExpressionChangedAfterItHasBeenCheckedError

Here is example app. https://stackblitz.com/edit/angular-1j7x8p?file=src%2Fapp%2Fmy-input.component.ts Open console. With Validators.compose([Validators.required]) all works.

1

1 Answers

1
votes

Hello you have got very interesting case.

So lets dive in what your code is doing

<form [formGroup]="formGroup">
  <my-input *ngFor="let field of fields" [formGroup]="formGroup" [field]="field"></my-input>
</form>

where

 formGroup = new FormGroup({}, { updateOn: 'submit' });

 fields = [
      { id: 'id', name: 'name' },
      { id: 'id2', name: 'name2', validators: { required: true } },
 ]

Here for each existing field in your fields in your AppComponent , Angular is rendering input field, and in the process of doing so it also fills the formGroup object in AppComponent

So in the beginning you have a valid formGroup, which is empty and valid. After iterating trough your fields collection for the first time, inside your my-input.component you are inserting a form control in the formGroup that is passed from the parent component in the ngOnInit life hook in the child component (While still being in the ngOnInit life hook of the parent element).

 If (!this.formGroup.contains(this.field.id)) {
    this.formGroup.addControl(
    this.field.id,
    New FormControl(
      undefined, 
      this.getValidators(this.field.validators)
    )
  );
}

You are also adding error validators based on the fields input coming from the parent via the following function.

private getValidators(validators) {
  if (validators) {
    const ngValidators = [];
    for (const validatorName in validators) {
      if (Validators[validatorName]) {
        ngValidators.push(Validators[validatorName]);
      }
    }
    return Validators.compose(ngValidators);
  }
  return Validators.compose([]);
}

At this point you have rendered the first input and filled the formGrop with one form control, which doesn't have any validators (because there were none in the field's configuration).

So now before entering the my-input.componentfor second time via the ngFor our formGroup is valid and we are still in the ngOnInit life hook of AppComponent (in other words the parent).

After we enter the my-input.component component for second time, we see that the second objects, from fields has validators property. Thanks to that we are adding now new formControl to the formGroup that we are using (from within my-input.component) and this form control, now has a required validator, but also it has no value inside, which immediately triggers the added validator, and makes the formGroup invalid. By doing so we are changing the state of the formGroup's Valid property and that triggers the expression changed after it has been checked error.

I hope that helped you out.