Skip to content

ExpressionChangedAfterItHasBeenCheckedError when reading form validity properties when validity changes during template execution #23657

Description

@CWSpear

I'm submitting a...

[x] Bug report 

Current behavior

If you have a composite form control (think a <my-address [formControl]="address"></my-address> component), if the internal form has a dynamic validator (i.e. [required]="isUSA"), the control will throw an ExpressionChangedAfterItHasBeenCheckedError error when the validator (in this example, isUSA value) changes:

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

Expected behavior

There to not be an ExpressionChangedAfterItHasBeenCheckedError error.

Minimal reproduction of the problem with instructions

Click on the Toggle Requiredness button, and note in the console that there is an ExpressionChangedAfterItHasBeenCheckedError error. (The circular JSON error seems to be generated from StackBitz.)

Demo: https://stackblitz.com/edit/angular-d9rsnv?file=app%2Fmy-composite-control%2Fmy-composite-control.component.ts

What is the motivation / use case for changing the behavior?

Template driven forms are easier to use, and template-driven (aka directive) validators are easier to work with and conditionally add and remove. You can do this similarly with reactive forms and you don't get this error.

Environment

Angular version: 5.2.8


Browser:
- [x] Firefox (desktop) version 59.0.3

Other information

Note that if you change the toggleRequiredness method to update the validators on the control, you don't get the error (but the point of this bug is that it should work by using purely template-driven means). For example, remove the required attribute from the input in the demo link above and update the toggleRequiredness method to this:

  toggleRequiredness() {
    this.nameRequired = !this.nameRequired;

    const control = this.form.controls['name'];

    if (this.nameRequired) {
      control.setValidators([Validators.required]);
    } else {
      control.clearValidators();
    }

    control.updateValueAndValidity();
  }

Demo (with the above modification): https://stackblitz.com/edit/angular-hhgkje?file=app%2Fmy-composite-control%2Fmy-composite-control.component.ts

Alternatively, you can manually trigger this.cdr.detectChanges(); when updating the requiredness. The argument of this bug is that this sort of behavior shouldn't be necessary with this kind of template-driven form.

Other reports related to validity changes during template execution

Specifically for validity, there does seem to be a potential to address this with a computed since the true/false value should not thrash by the end of the change detection (e.g. flip between true and false at the end of each cycle) and cause an infinite loop. errors/value/etc could cause infinite loops due to object equality and no clear way to compare values between separate change detection runs.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions