ExpressionChangedAfterItHasBeenCheckedError when *ngIf and asyncValidator use the same observable

This issue has been tracked since 2022-09-21.

Which @angular/* package(s) are the source of the bug?

zone.js

Is this a regression?

No

Description

<ng-container *ngIf="observable|async">
    <input formControlName="name">
  </ng-container>

When nameFormControl has an asyncValidator that depends on observable
The console prints out the following error:
ERROR
Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'. Find more at https://angular.io/errors/NG0100

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/angular-ivy-z9hej4?file=src/app/app.component.html

Please provide the exception or error you saw

ERROR
Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'. Find more at https://angular.io/errors/NG0100

Please provide the environment you discovered this bug in (run ng version)

See stackblitz
https://stackblitz.com/edit/angular-ivy-z9hej4?file=src/app/app.component.html

Anything else?

No response

JimmyRowland wrote this answer on 2022-09-24

Edit: console error disappears by using shareReplay({refCount: true}) but nested *ngIf still can't update properly without empty manual subscription in the component.

https://stackblitz.com/edit/angular-2ukcqu

ShareReplay solved my issue.
https://stackblitz.com/edit/angular-ivy-bnxukb?file=src/app/app.component.ts

The bug might be related to this:
https://stackoverflow.com/questions/51974367/async-pipe-subscription-not-work-correctly-when-nested-inside-ngif

alexkunin wrote this answer on 2022-09-25

Looks like the following happens when the timer "ticks":

  1. The input element is instantiated due to *ngIf.
  2. The element is attached to form control via FormGroupDirective#addControl which runs FormGroupDirective#updateValueAndValidity.
  3. Synchronous validators are being executed (AbstractControl#_runValidator), and the control becomes valid (status = 'VALID', valid = true, invalid = false)
  4. Asynchronous validators are being executed (AbstractControl#_runAsyncValidator), but right before that the control gains pending state (status = 'PENDING', valid = false, invalid = false).
  5. Asynchronous validators confirm the control is valid, and now we're back to state in p.3 above: status = 'VALID', valid = true, invalid = false.

So, status change happens twice within same digest cycle, which causes valid to change from true to false then to true again, and that's exactly what ExpressionChangedAfterItHasBeenCheckedError is about.

Commenting out template line with formGroup.valid check makes error message go away.

As a workaround, doing something like form.statusChanges.pipe(delay(1), map(v => v === 'VALID')) instead of form.valid should help.

More Details About Repo
Owner Name angular
Repo Name angular
Full Name angular/angular
Language TypeScript
Created Date 2014-09-18
Updated Date 2022-10-05
Star Count 84147
Watcher Count 3063
Fork Count 22247
Issue Count 1201

YOU MAY BE INTERESTED

Issue Title Created Date Updated Date