'Angular Reactive Forms AddControl Causes ExpressionChangedAfterItHasBeenCheckedError When Using PatchValue

I wanted to be able to build up a reactive form using components, but not set it all up in a single parent component. So I created the intial FormGroup and passed it to the components, which then do this.form.addControl(this.fb.group({ ... })); to add their group to the form.

Example

Parent Component

this.form = this.fb.group({}); // Empty

public ngAfterViewInit() {
  // Throws error after trying to patchValue on the child component
  this.form.get('example').patchValue({
    field1: 'Hello World!'
  });
}

Child Components

this.parentFormGroup.addControl(
  'childGroup', 
  this.fb.group({ 
    field1: [
      '', [Validators.required]
    ], 
    etc...
  }
);

This works until I try and this.form.get('childGroup').patchValue({ ... }) after making a request in the ngAfterViewInit lifecycle hook of the parent component. Instead of patching the group it throws an:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has 
changed after it was checked. Previous value: 'ng-valid: false'. Current value: 
'ng-valid: true'.

Is there a way to have child components add themselves to a FormGroup, and not have the parent setup the FormGroup controls and validations. We need to reuse the child components in different ways and combinations within the application so didn't want to keep repeating the FormGroup declaration outside of the child components, but this doesn't seem to work.



Solution 1:[1]

This error appears, because by patching the value in ngAfterViewInit you change the validity state of the form after the form has already been checked by the Angular change detection logic.

You have two options to prevent it:

  • You set up the form in a way to have directly the right validity state, e.g. by passing the initial value to the child and setting it there

or

  • You move setting the value to another change detection cycle by wrapping the patchValue into a setTimeout:

    setTimeout(() => this.form.get('childGroup').patchValue({ ... }))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 slim