ExpressionChangedAfterItHasBeenCheckedError in Angular — What, Why and How To Fix It?

ExpressionChangedAfterItHasBeenCheckedError
is without a doubt my favorite error in Angular applications.
I encountered this error a lot when I first started working with Angular. According to GitHub, so do many others.
When Will ExpressionChangedAfterItHasBeenCheckedError Be Thrown?
The most common reasons are:
- You are executing code in
AfterViewInit
which often happens when working withViewChild
, as it is undefined untilAfterViewInit
is called. - You are manipulating the DOM directly (e.g. using jQuery). Angular cannot always detect these changes and react properly.
- It can also happen due to race conditions when you are calling functions inside your HTML template.
What Is ExpressionChangedAfterItHasBeenCheckedError Trying to Warn Me About?
ExpressionChangedAfterItHasBeenCheckedError
is thrown when an expression in your HTML has changed after Angular has checked it (it is a very expressive error).
This error is only thrown in develop mode and for good reason; it is often a sign that you should refactor your code, as Angular warns you that this change in your expression will not be picked up when enabling production mode!
One of the reasons why production mode is faster than develop mode, is that Angular skips some checks (e.g. change detection after AfterViewInit
) that are done in develop mode. That means that code that’ll work fine in develop mode won’t work in production mode.
Here’s an example which would work fine in develop mode but not in production mode:
Another example: changing a @Input
property in ngOnInit
through an EventEmitter
can cause ExpressionChangedAfterItHasBeenCheckedError
as well.
How To Fix ExpressionChangedAfterItHasBeenCheckedError
Fix one: let Angular know to pick up the changes
As a quick fix, setTimeout
or ChangeDetectorRef
are often used to make the error disappear.
The latter is better, as with ChangeDetectorRef
the component view and its children are checked. On the other hand, setTimeout
will cause Angular to check the whole application for changes, which is way more expensive.
Fix two: move code away from ngAfterViewInit
Sometimes, the error is even easier to fix — just move your code to OnInit
.
Unless you have to rely on ViewChild
, or if some code should only run after Angular has fully initialized a component’s view, moving to OnInit
will solve your issue.
This is because Angular will perform change detection after OnInit
in both production and develop mode.
Fix three: use OnPush change detection
Don’t like Angular magic and how it detects changes? Just disable automatic change detection in your component and tell Angular yourself when it should detect changes.
The ChangeDetectionStrategy
can be specified in a component decorator to OnPush
. Now, Angular will only detect Input
changes automatically, and the rest is up to you.
While there is less magic involved by enabling theOnPush
strategy, you also get improved performance as there is less work for Angular to do. This can be useful for optimizing big and complex components.
On the other hand, you need to make sure you let Angular pick up changes. The prefered way to do that is using markForCheck
. If you do not make Angular aware of these changes, you will usually see UI errors or inconsistencies (e.g. a modal not disappearing) unless Angular does another change detection.
So, do not apply this over-eagerly without checking your component thoroughly.
Fix four: avoid mutations of @Input properties
@Input
properties of a component should ideally not be modified in the component itself. You avoid unexpected behaviour and side-effects by leaving the updating of @Input
properties up to the parent component. Instead of directly updating the @Input
property, you can emit the updated to the parent component which will update the @Input
property in the child component.
By the way: if you can’t avoid it then you can make use of the isAsync
parameter of EventEmitter to automatically trigger change detection (it calls setTimeout
internally).
Conclusion
You should now be able to understand when and why the infamous ExpressionChangedAfterItHasBeenCheckedError
occurs.
As you can see, there are multiple ways to deal with this error. Just make sure that you are actually solving the real underlying problem, instead of working around it. I’d be interested if you know other ways how to deal with this error.
Update 2021: the Angular documentation now contains a list of common errors including this one. For some errors there are even videos if you rather learn from videos than reading.