Angular Debugging "Expression has changed after it was checked": Simple Explanation (and Fix)

In this post, we will cover in detail an error message that you will occasionally come across while building Angular applications: "Expression has changed after it was checked" - ExpressionChangedAfterItHasBeenCheckedError.

We are going to give a complete explanation about this error. We will learn why it occurs, how to debug it consistently and how to fix it.

Most of all we are going to explain why this error is useful, and how it ties back to the Angular Development Mode.

Table Of Contents

In this post, we will cover the following topics:

  • Understanding the "Expression has changed" error, why does it occur?
  • The Angular Development mode
  • Debugging Techniques for finding the template expression that is causing the error
  • How to fix the "Expression has changed" error
  • Conclusions

We will first start by quickly debugging the error (a video is available for this), and then we will explain the cause and apply the fix. So without further ado, let's get started!

A common scenario where the error occurs

This type of error usually shows up beyond the initial development stages, when we start to have some more expressions in our templates, and we have typically started to use some of the lifecycle hooks like AfterViewInit.

Here is a simple example of a component that is already throwing this error, taken from this previous post:

This is a simple component that is displaying an Angular Material Data Table with a paginator, plus a loading indicator that get's displayed while we wait for the data to load.

Here is what this component looks like when the data is loaded:

Material Data Table

And here is what the component looks like when the data is loading:

Material Data Table

To find out why the "Expression has changed" error is being thrown in this situation, let's have a look at the simplified version of this component:

As we can see, we are using ngAfterViewInit(), because we want to get a reference to the Paginator page Observable, and the paginator is obtained using the @ViewChild() view query.

Whenever the user hits the paginator navigation buttons, an event is going to be emitted that triggers the loading of a new data page, by calling dataSource.loadLessons().

Note that the tap operator is the new pipeable version of the RxJs do operator!

Because the page Observable does not initially emit a value, we are emitting an initial value using startWith(). This causes the first page of data to be loaded, otherwise, data would only be loaded if the user clicks the paginator.

And here is the Data Source (simplified version):

As we can see, loadLessons() is emitting a new value for the loading$ Observable (it's setting the loading flag to true), and is doing so synchronously, before the asynchronous call to the backend.

Notice that this loading$ Observable is the one that is getting used in the ngIf expression that shows or hides the loading indicator.

Error Message Example

The code above will throw the following change detection error:

CourseComponent.html:13 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '[object Object]'. Current value: 'true'.
at viewDebugError (core.js:9515)
at expressionChangedAfterItHasBeenCheckedError (core.js:9493)
at checkBindingNoChanges (core.js:9662)

This error tells us that there is a problem with an expression in the template, but the question is, which expression? And why does it cause an error?

Debugging "Expression has changed after it was checked"

The debugging process that we will go over below is also done here step by step in this video, where we also explain the cause of the error:

Here is how we can identify the problematic expression. In the Chrome Dev Tools console, we have a call stack that identifies where exactly the error occurred.

Let's click on the link available in the first line of the call stack:

at viewDebugError (core.js:9515)

This will open the DevTools Javascript Debugger in the line where the error occurred: let's then add a manual breakpoint on that line.

If we now reload the component and trigger the error again, the breakpoint will hit and we will get the following:

Debugging ExpressionChangedAfterItHasBeenCheckedError

The program is now frozen at this point, and we can hover over the variables and go up and down the call stack, to see what is going on.

Notice the line 9515: that is where the error occurs, and the line number with the blue triangle is where we clicked to create the breakpoint.

We also have a call stack. If we start clicking on the functions up the call stack, we will see the function call to viewDebugError.

Identifying the previous value of the Expression

By highlighting the oldValue variable, we can see that the old value of the problematic expression was false, and according to the message it's now true:

Debugging ExpressionChangedAfterItHasBeenCheckedError

Identifying the Problematic Expression

But what template expression is causing this error? If we keep clicking up the call stack, we are going to see that a template expression will appear:

Debugging ExpressionChangedAfterItHasBeenCheckedError

As we can see, this is the ngIf expression that shows or hides the loading indicator: so this is the problematic template expression!

As we can see, the source maps generated by the Angular CLI are very useful to troubleshoot this kind of problem.

Understanding the "Expression has changed after it was checked" Error

This ngIf expression, at first sight, does not seem problematic. So why does this throw an error? Here is what happens:

  • the ngIf expression above is initially false because the data source is not loading any data, so loading$ emits false
  • if the loading$ Observable last emitted value is false, then the loading indicator should be hidden as no data is being loaded
  • while Angular is preparing to update the View based on the latest data changes, during that process it calls ngAfterViewInit, which triggers the loading of the first data page from the backend
  • loading the data would still take a while and it's an asynchronous operation, so the data will not arrive instantly
  • Here is the problem: as a synchronous call to dataSource. loadLessons() is made, a new true value of the loading$ flag is emitted immediately

And its this new value of the loading flag that accidentally triggers the error!

Let's learn why updating this flag during the view construction process is problematic.

The "View Updates Itself" Scenario

The problem here is that we have a situation where the view generation process (which ngAfterViewInit is a part of) is itself further modifying the data that we are trying to display in the first place:

  • the loading flag starts with false
  • we tried to display that to the screen, by hiding the loading indicator
  • due to the way ngAfterViewInit is written, the act of displaying the data itself further modifies the data
  • after the view is built, the loading flag is now true

So which value is the loading flag then, true or false? There is no way for Angular to decide and so it preventively throws this error, which only happens in Development Mode.

To learn more about the Angular Development Mode, have a look at this post. Right now, let's then see how we can fix this issue.

Understanding the Solution

So here is the solution: we can't use the paginator.page reference in ngAfterViewInit() and immediately call the Data Source, because that will trigger a further modification of the data before Angular could display it on the screen, so its not clear if the value of the loading flag should be true or false.

In order to solve this issue, we need to let Angular first display the data with the loading flag set to false.

Then, in some future time, in a separate Javascript turn, only then are we going to call the Data Source loadLessons() method, which will cause the loading flag to be set to true and the loading indicator will then get displayed.

Initial implementation of the solution

In order to defer the code inside ngAfterViewInit to another Javascript turn, here is one initial implementation that will help us to understand the solution better:

This already solves the problem: we don't have an error anymore!

As we can see, we are using setTimeout() to defer this code to another Javascript Virtual Machine turn, and notice that we are not even specifying a value for the timeout.

Let's now have a look at an alternative implementation with less code nesting, and then we will explain why this fixes the issue.

An alternative using RxJs

This is an alternative version that looks better due to less code nesting, and uses the RxJs pipeable operator delay:

How does setTimeout or delay(0) fix this problem?

Here is why the code above fixes the issue:

  • The initial value of the flag is false, and so the loading indicator will NOT be displayed initially
  • ngAfterViewInit() gets called, but the data source is not immediately called, so no modifications of the loading indicator will be made synchronously via ngAfterViewInit()
  • Angular then finishes rendering the view and reflects the latest data changes on the screen, and the Javascript VM turn completes
  • One moment later, the setTimeout() call (also used inside delay(0)) is triggered, and only then the data source loads its data
  • the loading flag is set to true, and the loading indicator will now be displayed
  • Angular finishes rendering the view, and reflects the latest changes on the screen, which causes the loading indicator to get displayed

No error occurs this time around, and so this fixes the error message.

Moving the initialization of the data to ngOnInit()

But in this case, an even better solution exists! The core of the problem is that we are modifying the data being displayed (the loading flag) inside ngAfterViewInit().

So let's remove the call to startWith(null) that loads the initial page, and instead, lets trigger the loading of the initial data in ngOnInit():

This also solves the issue, with this we don't have the error anymore.

With this new version, there is no modification of the template data in the ngAfterViewInit() lifecycle hook, and so the problem does not occur.

Let's now wrap things up by talking about what would happen if this error would NOT be thrown.

Conclusions

In summary, Angular protects us from building programs that are hard to maintain in the long-term and reason about, by throwing the error "Expression has changed after it was checked" (only in development mode).

Although a bit surprising at first sight, this error is very helpful!

Why "Expression has changed after it was checked" is useful

What would happen if the view generation process could itself modify the rendered data? This could be very problematic, to start we could even create an infinite loop!

More commonly, here is what would happen: imagine having a UI that behaves in an erratic way, where sometimes the user cannot see the data in our component, and randomly sees some previous version of the data.

Then the user clicks or hovers some unrelated UI elements which happen to trigger an event handler, and now another unrelated components is affected. This kind of error can be very hard to reproduce, troubleshoot and reason about.

One of the main goals of using a web framework like Angular is the guarantee that the data in our components will always get reflected correctly in the view, and that we don't have to do that synchronization ourselves.

The Angular Development Mode helps us to avoid building UIs that are hard to troubleshoot and reason about, by issuing this error message during development that helps us to fix the issue early in the development process.

I hope that this post helps with this type of error and that you enjoyed it! If you have some questions or comments please let me know in the comments below and I will get back to you.

To get notified of upcoming posts on multiple Angular topics, I invite you to subscribe to our newsletter:

Video Lessons Available on YouTube

Have a look at the Angular University Youtube channel, we publish about 25% to a third of our video tutorials there, new videos are published all the time.

Subscribe to get new video tutorials: