Two easy ways to trigger `ExpressionChangedAfterItHasBeenCheckedError` in Angular
Okay, so were happily developing your Angular application, when you see the developer's favorite ExpressionChangedAfterItHasBeenCheckedError
.
Won't keep you in suspense:
This error appears because you've changed a property that is used in the template of a parent component, from a child component, while change detection is running.
The reason for this is something called unidirectional data flow.
In practice, this means that data in Angular flows downward during change detection. A parent component can easily change values in its child components because the parent is checked first. A failure could occur, however, if a child component tries to change a value in its parent during change detection (inverting the expected data flow), because the parent component has already been rendered. In development mode, Angular throws the ExpressionChangedAfterItHasBeenCheckedError error if your application attempts to do this, rather than silently failing to render the new value.
On whatever lifecycle hook you try, if you trigger a change from a child to the parent properties used in the template, you will get this error.
The EASY WAYS to do this are:
1. Emit a parent change from the child on one of the lifecycle hooks
For example, in the child component you have
@Output() changeParent = new EventEmitter<any>();
ngOnInit(): void {
this.changeParent.emit();
}
And in the parent component, you change a property when this emit event is captured; For example:
text = 'some text';
changeText(): void {
this.text = 'some other text'
}
While the parent template looks like
{{text}}
<app-child (changeParent)="changeText()"></app-child>
You get this beautiful result: ERROR RuntimeError: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'some text'. Current value: 'some other text'
2. Use a shared service to change a property in the parent
For example, in a grand-child component
ngOnInit(): void {
this.loaderService.setLoading(true);
}
While in the grand-parent component you have
isLoading$: Observable<boolean> = this.loaderService.isLoading$;
And in the template
{{isLoading$ | async}}
or
<other-component [isLoading]="isLoading$ | async></other-component>
And the shared service has a subject that is initially set to false;
You get this beautiful result: ERROR RuntimeError: G0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'
There are some solutions to this:
- Make the change emitter async:
async ngOnInit(): Promise<void> {
await this.http.get('api/users').toPromise();
this.triggerChange();
}
or (bleah)
ngOnInit(): void {
setTimeout(() => {this.triggerChange()}, 0);
}
- Move the content you want to change in its own component
For example, you take the isLoading$: Observable<boolean> = this.loaderService.isLoading$;
and move it in the other-component. Then you load this component in the parent component and you're done.
This was the original article that explained how this error occurs, but things changed a bit since then.
Watch me walk you through some code with examples that show the triggering of this error and some possible solutions.