Omtechguy
Omtechguy

Reputation: 3651

Child ControlValueAccessor Component with formgroup - expression has changed after it was checked

on loading of my parent component, i am updating the form of that (parent) component by using patchValue.

on of that form components i my child ControlValueAccessor component that also has a formgroup. therefore, on my writeValue function i am updating the child component regarding the value that passed from the parent component patchValue.

my writeValue function looks like that:

    writeValue(value: CountryAndCity): void {
    if (value != null && value.country && value.city) {
      this.isDefaultValueDefined = true;
      this.form.patchValue({country: value.country});
      this.form.patchValue({city: value.city});
      **this.cdRef.detectChanges();**
    }
  }

As you can see, i had to use detectChanges() as the last line of the above function because without it i am getting the error:

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

i think i understand why is that. Angular start the check of the parent component, then, start to check the child component but then, on the child component the child component, on the writeValue function changing the parent component form state.

i feel uncomfortable with the use of detectChanges(). is there a way to do something else? is it a must when using parent and child components that each of them has formGroup and the child component is one of the parent formgroup formControl?

Upvotes: 13

Views: 6319

Answers (3)

maxime1992
maxime1992

Reputation: 23803

Your approach is good and @Tobi's answer is good too!

That said, if you do not want to struggle too much in building nested forms (using sub components)/creating custom control value accessors you should definitely checkout this library: https://github.com/cloudnc/ngx-sub-form

The error you encountered should never happen if you use the library as we handle that case pretty much like Tobi did.

From your point of view it'll just require you to extend a class and not much more! Will also give you type safety, access to nested errors, etc.

I've also replied to a question on how to build complex/nested/sub forms, this might be of interest for you https://stackoverflow.com/a/56375605/2398593

Edit:

If you want to go further, I've just published a blog post to explain a lot of things about forms and ngx-sub-form here https://dev.to/maxime1992/building-scalable-robust-and-type-safe-forms-with-angular-3nf9

Upvotes: 0

Tobi
Tobi

Reputation: 2040

I don't think, that this is happening because of your write function but rather that something in your form is emitting values right after the child has been created, but it is hard to tell without seeing more of your code.

I had the same problem so what I did to prevent this error without manually triggering change detection was to decouple the value propagation to the parent from the current change detection cycle.

An easy way to achieve this is to yield in the value upstream like

this.form
  .valueChanges
  .pipe(
    delay(0)
  )
  .subscribe(it => {
    this.propagateChange(...)
  })

I do not know if this is the best approach but for me it works.

BTW: I would also recommend using {emitEvent: false} as patch options, which prevents the child from firing the event in the write function. This would solve your problem if your assumption about the source of the error is true, but wouldn't if my assumption was true.

Upvotes: 2

B0ltz
B0ltz

Reputation: 425

Angular application state change could comes from :

  • Events
  • XHR
  • Timers

But when you are writing your value with your Control Value Accessor you need to tell angular update the changes with ChangeDetectorRef and detectChanges()

You should have a look at this part Who notifies Angular of article from Pascal Precht

Upvotes: 0

Related Questions