angular: Calling setValidators on a form control when control is nested as a component in form causes ExpressionChangedAfterItHasBeenCheckedError
I’m submitting a …
[ ] Regression (behavior that used to work and stopped working in a new release)
[ X] Bug report
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
Current behavior
Calling setValidators on a form control when control is nested as a component in form causes ExpressionChangedAfterItHasBeenCheckedError. Otherwise nesting formControls in components seems to work fine.
Expected behavior
When calling myform.controls[“my_control”].setValidators([Validators.required]); in a nested component of a reactive form I would expect no errors to occur, especially ExpressionChangedAfterItHasBeenCheckedError
Minimal reproduction of the problem with instructions
What is the motivation / use case for changing the behavior?
Please tell us about your environment
Angular version: ~4.2.0
Still an issue
Browser:
- [ X] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
For Tooling issues:
- Node version: 6.11.0
- Platform: linux
Others:
About this issue
- Original URL
- State: open
- Created 7 years ago
- Reactions: 18
- Comments: 25 (2 by maintainers)
We’re experiencing a related issue: ExpressionChangedAfterItHasBeenCheckedError is always thrown for a very simple reactive form, if the input is on a lower template hierarchy level (defined by
*ngFor
/*ngIf
etc) than the code accessing the form’s validity:Here’s a working plunkr.
The reason for the error being thrown is that change detection evaluates the
*ngIf
of the error div first before it evaluates the input’s validity, as the input is one level lower in terms of template hierarchy.The workaround is to bring the error div onto the same level as the input by adding just another
<div *ngIf="true"></div>
around it (as mentioned above).Work around my issue like this
Promise.resolve().then(()=>{ this.control.setValidators(this.buildValidators()); this.control.updateValueAndValidity(); });
I ran change detection manually in the
ngAfterViewInit
lifecycle hook of the parent component which did the trick (for me).The reason is that that hook is executed after the view AND the child views are initialized and thus the validators from the child component should be in place at that point.
Just stumbled upon this issue and it is reaaaaaally nasty one, I know that it will not matter in production but in dev env is really a show stopper.
A workaround for us was setting the component that holds the form to use OnPush detection strategy.
@renehamburger thank you so much 🙏 for clarifying that. I’ve encountered that issue plenty of times thinking I was doing something wrong and turns out… not really (?). At least from the point of view of CD.
It turns out this is happening for one nested form component where it wasn’t happening before nesting. For instance I’m passing in the formGroup via an input. What’s special about this component is that is makes use of ngDefaultControl [(ngModel)]=“rE.formattedDate” and when I set [Validators.required] in the parent form and change the input value the feedback loop starts. For now I’ve found a temporary fix by putting *ngIf=“true” on the nested component which contains the component using ngDefaultControl and I can still use the validators. I think it’s interesting this worked when not nested. I wonder if I should rewrite the component to not use ngDefaultControl [(ngModel)] or if this is even a bug. BTW this seems to complicated to put in a plunker but I’ll take a look to see if that’s feasible.
this error poped after upgrade to ivy
ExpressionChangedError on form.valid with following structure
`<custom-comp formGroup=“form” [isValid]=“(form.statusChanges | async) == ‘VALID’”>
<nested1 formControlName="nested1></nested1>
<nested2 formControlName="nested2></nested2>
</custom-comp><button [disabled]=“!((form.statusChanges | async) == ‘VALID’) || loading”
workaround: replace form.valid with (form.statusChanges | async) == ‘VALID’
Just to add what I’m seeing from my tests, I was only seeing the issue if the form’s validity changed below the 2nd level of components, i.e. the 3rd level or lower.
Here
<child1>
would add a control with no validators, so the form is valid. And then <child2> adds a control with an empty string, and therequired
validator, making the form invalid. This will cause theExpressionChangedAfterItHasBeenCheckedError
. If I instead made<child2>
a sibling to<child1>
, it does not throw the error when form.valid changes to false. I was able to use the*ngIf="true"
workaround in this scenario by placing it around<child1>
Facing same issue. Here’s a very simple repro that only uses nested reactive form and ng-if. https://plnkr.co/edit/GrvjN3sJ05RSNXiSY8lo
I am facing the same issue with the nested form components. Please let me know if their is any updates on this issue.