The expression has changed after it was checked (Expressionchangedafterithasbeencheckederror) is one of the common errors, that we encounter in Angular applications. The error arises when the binding expression changes after angular checked it during the change detection cycle.
Table of Contents
ExpressionChangedAfterItHasBeenCheckedError
To understand the error, let us create an app and try to produce the error.
Create a new angular application. Add child.component.ts
with the following code. It has one form element message
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import { Component } from "@angular/core"; @Component({ selector: "app-child", template: ` <h2>Child Component</h2> message: <input [(ngModel)]="message"> `, }) export class ChildComponent { message = "" } |
Open app.component.ts
and add following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import { Component, ViewChild } from "@angular/core"; import { ChildComponent } from "./child-component"; @Component({ selector: "my-app", template: ` <h1>ExpressionChangedAfterItHasBeenCheckedError</h1> <app-child> </app-child> <br> `, }) export class AppComponent { message=""; @ViewChild(ChildComponent) childComp: ChildComponent; ngAfterViewChecked() { console.log('AppComponent==>AfterViewChecked'); this.message=this.childComp.message; } } |
- We are using the
ViewChild
query to get the reference of theChildComponent
(childComp
) this.message=this.childComp.message;
updates themessage
property of this component with that ofChildComponent
. We are not doing anything with this property.
Run this app. Type Hello
it will work correctly and you will not see any errors.
Now, add the following in the template of AppComponent
1 2 3 | message from child {{message}} |
Run the app. Now as soon as you start typing Hello
, the Angular will throw the error ExpressionChangedAfterItHasBeenCheckedError
, which you can see in the developer console.
The above error appears only in the development environment. If you enable the production environment, you will no longer see the error.
Reason for the error
To understand the error, we need to understand what is change detection & how it works.
What is Change Detection ?
Change detection (CD) is the mechanism by which angular keeps the template in sync with the component.
Now, look at the following template from the app.component.ts
. Angular needs to evaluate the string interpolation expression {{message}}
and replace it with the actual value and update the DOM. It must do it every time, the value of the message
changes.
1 2 3 | message from child {{message}} |
To do that, Angular runs a change detection process, which checks the value of every such binding in the component. If the current value of such binding is different from its previous value (oldValue), then it will update the DOM.
For the change detection work properly, the angular must run change detection on every event that may lead to a change. The following are some of the events that may lead to a change in the state of the application.
- Browser events like click, keyup, mouse movement, etc
- Child Component raising an event to notify the parent
- Sending HTTP Requests to the backend server for a data
- Observable / Promises emits a new result
- Timer events like setInterval() and setTimeout()
Hence, Angular starts a change detection, whenever the above events take place.
Now, there are two important points to remember
- Change Detection is unidirectional and always starts from the root component irrespective of where the change has occurred.
- It evaluates each binding only once in each change detection cycle and then it updates the DOM if necessary
Back to the Error
Now, let us see what is happening in our above code.
- Initially, the value of
message
is empty - We enter
h
in the input element. This starts a change detection cycle. - It checks the value of
message
property. Its value is empty. Hence updates the DOM with empty string. - Angular fires the
AfterViewChecked
hook - AfterViewChecked updates the message property to h.
- Angular runs another check to see if all the binding’s values are correct. It detects the value of
message
property is nowh
. which is different from when it checked it in step 3. It raises theExpressionChangedAfterItHasBeenCheckedError
- Change detection cycle ends
Also, note that h is not displayed as soon as you type it. To display it we need to start another change detection, which we start just by clicking elsewhere in the browser window.
Error appears only in development mode
Angular runs another check only in the development mode. It helps us to identify & rectify the error.
Angular could easily update the bindings & DOM in the second check. But such a check may potentially introduce new changes in the bindings. Hence, we may actually enter into an infinite loop. In Fact, that is what happened in the AngularJS digest cycle.
Angular does not run another check in the production environment for performance reasons. Because It is just a waste of crucial CPU time. Hence you won’t see any errors thrown in the console window. That does not mean that error does not exist.
Examples & Solutions to Expression Changed After It Has Been Checked Error
There is no single right solution to the ExpressionChangedAfterItHasBeenCheckedError
problem. The solution depends on each use case.
The most important is to find out where and why the error is happening. And based on that refactor the code.
In the example code above, instead of the parent reading the child component property, it would be better if the child component raises an event, whenever the model changes using the output & EventEmitter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import { Component, EventEmitter, Output } from "@angular/core"; @Component({ selector: "app-child", template: ` <h2>Child Component</h2> <input [(ngModel)]="message" (ngModelChange)="messageChangeFn($event)" /> `, }) export class ChildComponent { message = "" @Output() messageChanged: EventEmitter<any> = new EventEmitter<any>(); messageChangeFn($event) { this.messageChanged.emit(this.message); } } |
In the parent component use the event binding to read the message whenever it changes in the child component.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import { Component, ViewChild } from "@angular/core"; import { ChildComponent } from "./child-component"; @Component({ selector: "my-app", template: ` <h1>ExpressionChangedAfterItHasBeenCheckedError</h1> <app-child (messageChanged)="messageChanged($event)"> </app-child> <br /> message from child {{ message }} ` }) export class AppComponent { message = ""; @ViewChild(ChildComponent) childComp: ChildComponent; messageChanged(message) { this.message = message; } } |
Check AfterViewChecked & AfterContentChecked hook
Angular invokes AfterViewChecked
after it checks all the bindings. AfterContentChecked
is triggered when angular finishes checking of the projected content.
Hence if you are using these hooks, you should make sure that they do not change any binding that angular has already checked. Better to avoid using these hooks as angular invokes them during every change detection cycle.
For any initialisation you can make use of OnInit hook.
ViewChild & Expression Changed Error
Look at this question from stackoverflow.com. The code is shown below (also check the stackblitz.com.).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | //our root app component import { Component, NgModule, VERSION, ViewChild } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; @Component({ selector: "my-app", template: ` <div> <div #box class="box">{{ getData() }}</div> <button *ngIf="isShowExpand()">Expand</button> </div> ` }) export class AppComponent { data = "loading data..."; @ViewChild("box") boxElement; constructor() { this.fetchData(); } getData() { return this.data; } fetchData() { setTimeout(() => { this.data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; console.log("data arrives"); }, 1000); } isShowExpand() { let show = this.boxElement && this.boxElement.nativeElement.scrollHeight > this.boxElement.nativeElement.clientHeight; console.log("show: =>" + show + " box => " + this.boxElement); return show; } ngDoCheck() { console.log("AppComponent==>ngDoCheck"); } ngAfterContentInit() { console.log("AppComponent==>ngAfterContentInit"); } ngAfterContentChecked() { console.log("AppComponent==>ngAfterContentChecked"); } ngAfterViewInit() { console.log("AppComponent==>AfterViewInit"); } ngAfterViewChecked() { console.log("AppComponent==>AfterViewChecked"); } } |
The culprit here is the boxElement
, which is undefined
when the component is loads for the first time. The following is the sequence of events that takes place.
- Angular Instantiates the component. The
boxElement
isundefined
. - The change detection cycle begins. (steps 1 to 6)
- Angular starts to check the view and evaluates
isShowExpand()
. It returnsundefined
becauseboxElement
isundefined
. (step 4 ) - Angular updates the ViewChild query and updates the
boxElement
. Raises theAfterViewInit
&AfterViewChecked
(step 5 & 6 ) - Angular runs another check to see if all the bindings values are correct. If detects
isShowExpand()
method returnsfalse
Hence it raises theExpressionChangedAfterItHasBeenCheckedError
(step 7 & 8) - Angular runs second CD on app startup. (steps 9 to 13).
- Data Arrives from the back end (step 15).
- This triggers another change detection (step 16 to 20 )
isShowExpand()
returnstrue
(step 18)- Angular runs another check.
isShowExpand()
returns true, Hence no error is thrown.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 1. AppComponent==>ngDoCheck 2. AppComponent==>ngAfterContentInit 3. AppComponent==>ngAfterContentChecked 4. show: =>undefined box => undefined 5. AppComponent==>AfterViewInit 6. AppComponent==>AfterViewChecked 7. show: =>false box => [object Object] 8. ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'false'. 9. Angular is running in development mode. Call enableProdMode() to enable production mode. 10. AppComponent==>ngDoCheck 11. AppComponent==>ngAfterContentChecked 12. show: =>false box => [object Object] 13. AppComponent==>AfterViewChecked 14. show: =>false box => [object Object] 15. data arrives 16. AppComponent==>ngDoCheck 17. AppComponent==>ngAfterContentChecked 18. show: =>true box => [object Object] 19. AppComponent==>AfterViewChecked 20. show: =>true box => [object Object] |
The Stack Overflow solution uses the AfterViewChecked
to trigger another change detection. But the AfterViewChecked
runs on every CD, hence it is not an optimal solution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | //our root app component import { ChangeDetectorRef, Component, NgModule, VERSION, ViewChild } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; @Component({ selector: "my-app", template: ` <div> <div #box class="box">{{ getData() }}</div> <button *ngIf="show">Expand</button> </div> ` }) export class AppComponent { data = "loading data..."; show = false; @ViewChild("box") boxElement; constructor(private cd: ChangeDetectorRef) { this.fetchData(); } getData() { return this.data; } fetchData() { setTimeout(() => { this.data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; console.log("data arrives"); setTimeout(() => { this.show = this.isShowExpand(); }); }, 1000); } isShowExpand() { let show = this.boxElement && this.boxElement.nativeElement.scrollHeight > this.boxElement.nativeElement.clientHeight; console.log("show: =>" + this.show + " box => " + this.boxElement); return show; } } |
First, we create a property show
and use it to bind to the ngIf
. This will make the CD quicker as it does not have to call the isShowExpand
method on every CD.
Since we need to update the show
property only when we receive the data, we use the fetchData
method to update it.
1 2 3 4 5 | setTimeout(() => { this.show = this.isShowExpand(); }) |
After the data arrives we need to wait for angular to render the data. Otherwise scrollHeight
& clientHeight
will not be correctly updated. Hence, we use the setTimeout
to wait for a tick before calling the isShowExpand
method. The setTimeout
triggers a change detection.
ngIf and ExpressionChangedAfterItHasBeenCheckedError
The expressions we use in ngIf
is one of the common reason for ExpressionChangedAfterItHasBeenCheckedError. Consider the following example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | <h1> Angular-Expression Changed After It Has Been Checked Error Demo </h1> <p> Note: If age equal Adult, then it should choose married option. If not, then no need to choose married option </p> <form #form="ngForm"> <div> <em *ngIf="age.invalid">Required</em> <label>Age</label> <select required name="age" ngModel #age="ngModel"> <option value="" disabled>Choose</option> <option value="1">Child</option> <option value="2">Adult</option> </select> </div> Married Control Status {{ married.invalid }} <!-- No Error --> <div> <em *ngIf="married.invalid">Required</em> <!-- Error Here --> <label>Married</label> <select [required]="age.value === '2'" name="married" ngModel #married="ngModel" > <option value="">Choose</option> <option value="1">Married</option> <option value="2">Not Yet</option> </select> </div> </form> <div> <ul> <li>Step 1: Press F12, Turn on Dev tool</li> <li>Step 2: Age options => select to Adult</li> <li>Step 3: Error message shown</li> <li>Step 4: Click any space on page</li> <li>Step 5: Married [Required] message Shown</li> </ul> </div> |
- The
age
field accepts valuesChild
(1) &Adult
(2). - If
age
isAdult
(i.e.age.value===2
) thenmarried
field is required else not.
Run this app and select age
as Adult
. The error is thrown immediately.
Error is thrown in the ngIf
expression where we use married.invalid
.
1 2 3 4 | <em *ngIf="married.invalid">Required</em> <!-- Error Here --> |
But the following line does not throw the error although we use the same expression married.invalid
1 2 3 4 5 | Married Control Status {{ married.invalid }} <!-- No Error --> |
- When the app starts the
married.invalid
isfalse
- We change the age to
Adult
. Theage.value
updated to2
. , but themarried.invalid
is stillfalse
. Angular is yet to update it - Angular starts the change detection.
ngIf
is structural directive. It is treated as a child view, Hence Angular starts the change detection ofngIf
first. It evaluates themarried.invalid
(which isfalse
), which is an input to thengIf
directive.- Once angular finishes all the child components, it will start checking the current component. Updates
married.invalid
totrue
- Now, Angular runs another check It raises the error when it checks the
ngIf
expression.
Solution
The solution is to control the evaluation of ngIf
expression. Change the expression to a property in the component class marriedinvalid
.
1 2 3 | <em *ngIf="marriedinvalid">Required</em> |
Next, use the ModelChange
to listen to the changes to the age
& married
input fields.
1 2 3 4 5 6 7 8 | <select required name="age" ngModel #age="ngModel" (ngModelChange)="modelChanged($event)"> |
1 2 3 4 5 6 7 8 9 | <select [required]="age.value === '2'" name="married" ngModel #married="ngModel" (ngModelChange)="modelChanged($event)" > |
Finally, in the component code listen to the modelChanged
. Update the marriedinvalid
accordingly. We use the setTimeout
to wait for a tick before updating the marriedinvalid
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import { Component, ViewChild } from "@angular/core"; import { NgModel } from "@angular/forms"; @Component({ selector: "my-app", templateUrl: "./app.component.html", styleUrls: ["./app.component.css"] }) export class AppComponent { marriedinvalid = false; @ViewChild("married", { static: false, read: NgModel }) marriedRef; modelChanged() { setTimeout(() => { this.marriedinvalid = this.marriedRef.invalid; console.log("modelChanged " + this.marriedinvalid); }); } } |
References
Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError
error
Read More
- Angular Tutorial
- Typescript Tutorial
- Component Life Cycle
- ViewChild
- Input, Output & EventEmitter
- OnInit & OnDestroy
- OnChanges
- DoCheck
- AfterViewInit, AfterViewChecked, AfterContentInit & AfterContentChecked
Thank you for this insightful post! The detailed explanation of the “ExpressionChangedAfterItHasBeenCheckedError” and the practical examples really helped clarify the concept for me. I appreciate the tips on how to resolve this error. Looking forward to more posts like this!
Using setTimeout to “make something work” is code smell. Also there is no guarantee you will have awaited the next “tick”.