Skip to content

Calling setValidators on a form control when control is nested as a component in form causes ExpressionChangedAfterItHasBeenCheckedError #18004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
rmanuel200 opened this issue Jul 8, 2017 · 25 comments
Labels
Milestone

Comments

@rmanuel200
Copy link

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:

@Toxicable
Copy link

Can you make a minimal reproduction to show your issue? Here's a plunkr to get your stared: https://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5

@rmanuel200
Copy link
Author

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.

@BioPhoton
Copy link
Contributor

Hi @rmanuel200
I was not able to get the point. But i just read #18748 and tried your *ngIf workaround. It solves the problem in #18748
I bet you have the same problem!! Try move the html where you use the *ngIf under the ngModel dir.

Please let me know if my my assumption is the case!

best,
Michael

@renehamburger
Copy link

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:

<form [name]="formName"
      [formGroup]="formGroup">
  <div *ngIf="true">
    <input formControlName="name"
           required
           placeholder="Please enter you name">
  </div>
</form>
<div *ngIf="formGroup.valid">
  Name is required.
</div>

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).

@rmanuel200
Copy link
Author

I was able to trigger the ExpressionChangedAfterItHasBeenCheckedError with form validators error in this plunker using a simpler example with a mutable object:

https://plnkr.co/edit/MtAvKgSnudA1Lk7WEVL2?p=preview.

We are passing in [formGroup] through inputs on many forms and this problem wasn't happening prior. Please inform if this is something that's fixable.

@sairamponugoti
Copy link

I am facing the same issue with the nested form components.
Please let me know if their is any updates on this issue.

@theactiveactor
Copy link

theactiveactor commented Jan 15, 2018

Facing same issue. Here's a very simple repro that only uses nested reactive form and ng-if.
https://plnkr.co/edit/GrvjN3sJ05RSNXiSY8lo

@jasonzhang2022
Copy link

@jasonzhang2022
Copy link

In my case, I can't try *ngIf.

  1. I want to define a template reference variable for the component in ngIf.
  2. I am creating a component library. I can't ask the library user to add *ngIf="true" statement in their template.

@jasonzhang2022
Copy link

Work around my issue like this
Promise.resolve().then(()=>{
this.control.setValidators(this.buildValidators());
this.control.updateValueAndValidity();
});

@mccumb01
Copy link

mccumb01 commented Apr 14, 2018

Same issue when trying to setValidators inside a nested reactive forms component.

To clarify the above <div> idea, since the explanations weren't clear to me & the plunkers don't clearly show the problem or the solution:

In the Parent template, add a <div> AROUND the nested component as shown below, with *ngIf=true in the opening <div> tag. This allows you to add/remove validators in the nested component w/o this error.

<parentFormComponent formGroupName="parent" *ngIf="someThing != null">
  <div *ngIf="true">
    <nestedComponent [parentForm]="parent"></nestedComponent>
  </div>
</parentFormComponent>

@jasonzhang2022 's solution w/o messing w/the template would be nice, but didn't work for me. A bit more context would be helpful.

@cjablonski76
Copy link

any update on this issue? I am experiencing the same issue now that I'm trying to make some nested reactive forms.

@cjablonski76
Copy link

cjablonski76 commented Aug 9, 2018

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.

<app-component>
   <form [formGroup]="myForm">
      <child1 [parentForm]="myForm">
       // ngOnInit() { this.parentForm.addControl('child1', new FormControl('')); }
            <child2 [parentForm]="myForm">
             // ngOnInit() {this.parentForm.addControl('child2', new FormControl('', Validators.required));}
            </child2>
      </child1>
   </form>
</app-component>

Here <child1> would add a control with no validators, so the form is valid. And then adds a control with an empty string, and the required validator, making the form invalid. This will cause the ExpressionChangedAfterItHasBeenCheckedError.
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>

<div *ngIf="true">
    <child1>
         <child2></child2>
    </child1>
</div>

@moxival
Copy link

moxival commented Oct 12, 2018

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.

@lodnert
Copy link

lodnert commented Oct 22, 2018

+1

@Jonatthu
Copy link

		setTimeout(() => { this.dynamicFilterFieldForm.get(field.name).updateValueAndValidity(); });

helped me

@maxime1992
Copy link

@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.

@ricardosaracino
Copy link

Spent a bit of time refining this one .. hopefuly helps someone

const cbTouch = (control: AbstractControl) => {

      control.markAsTouched();

      control.updateValueAndValidity();

      if (control instanceof FormGroup) {

        (<any>Object).values(control.controls).forEach(control1 => {

          cbTouch(control1);

          if (control1.value instanceof FormArray) {
            control1.value.controls.forEach(value => cbTouch(value));
          }
        });
      }
    };

    cbTouch(this.form);

@SamVerschueren
Copy link
Contributor

SamVerschueren commented Jun 6, 2019

I ran change detection manually in the ngAfterViewInit lifecycle hook of the parent component which did the trick (for me).

ngAfterViewInit() {
	this.cdRef.detectChanges();
}

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.

@Londeren
Copy link

Londeren commented Jun 18, 2019

The issue is still here. Fix (hack) by @renehamburger works fine.

@ricardosaracino
Copy link

	this.cdRef.detectChanges();

do you have a more complete code example... i have just been ignoring the errors 😒

@camueller
Copy link

While it is good to know that there are work-arounds like manual change detection this issue should be handled by the framework.

@robvaneck
Copy link

when is this going to be fixed? Im still having issues with this

@Brandinga
Copy link

Brandinga commented Apr 7, 2020

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>

<nested2 formControlName="nested2>

<button [disabled]="!((form.statusChanges | async) == 'VALID') || loading"

        (click)="submit('default')">

  <i [class]="loading ? 'fa fa-spinner fa-spin' : 'fa fa-check'"></i>buchen</button>`

workaround: replace form.valid with (form.statusChanges | async) == 'VALID'

@gustavshf
Copy link

I'm having the same issue. :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests