Fixing Expression Has Changed After It Was Checked Error in Angular

In this post, I want to show you how to fix a common Angular error: Expression has changed after it was checked.

error

During development, you might encounter this error at some point. Here is a hint: "The previous value was 'loading...', but the current value is 'all done loading'." As you can see, this occurs in the AppComponent.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, CommonModule],
  template: '<div>{{message}}</div>',
  styleUrl: './app.component.css',
})
export class AppComponent {
  message: string = 'loading...'

  ngAfterViewInit() {
    this.message = 'all done loading'
  }
}

Here is our AppComponent with a single rendered variable message. By default, the value is loading... and later we change it to all done loading.

With such little code, we get this error, and it is confusing for most people.

The first thing I want to mention here is that this error is rendered only in development mode. If we build our Angular application for production, we never see this error. Now we must understand why this error occurs at all.

In development mode, when Angular runs its change detection cycle, it does so twice, directly one after another. So we are getting two cycles.

A lot of people think that this is completely useless. But it is not useless.

Angular checks that all values remain the same and are not changed.

If they are changed between checks, then you have some nasty mutations or side effects that should not be there.

Inside our code, we are changing the message inside the ngAfterViewInit hook. The main problem is that we changed the value, but the change detection cycle was not triggered. This change is not reflected in the markup. Angular compares the bindings of all properties and sees that this property was changed. This is exactly what this error indicates.

The most important thing to remember is that if you have anything that changes bindings inside the template, you must run a new change detection cycle afterward.

Ways to Fix

Let's take a look at all the ways we can fix this error. The first way is to update our message not inside ngAfterViewInit but inside ngAfterContentChecked. This fixes our error.

This helps because ngAfterViewInit happens after DOM rendering, but ngAfterContentChecked is done before ngAfterViewInit. So, we update our value before the DOM update happens. It is similar to writing our logic inside ngOnInit or inside a constructor.

Another way to fix it is by manually running change detection.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, CommonModule],
  template: '<div>{{message}}</div>',
  styleUrl: './app.component.css',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
  message: string = 'loading...'

  ngAfterViewInit() {
    this.message = 'all done loading'
  }
}

Here we just added OnPush change detection and left the code as it was previously. We won't get any error, but our message won't be changed either.

loading

So our changes are not reflected inside the view.

With OnPush you will get fewer errors, but often you need to trigger the change detection cycle manually.

For example, in our case here.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, CommonModule],
  template: '<div>{{message}}</div>',
  styleUrl: './app.component.css',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
  message: string = 'loading...'
  cdr = inject(ChangeDetectionRef)

  ngAfterViewInit() {
    this.message = 'all done loading'
    this.cdr.detectChanges()
  }
}

Now it works completely correctly in the browser. It's the best fix for this specific use case.

But we can also do it differently. If we just wrap our code with setTimeout, it will also help.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, CommonModule],
  template: '<div>{{message}}</div>',
  styleUrl: './app.component.css',
})
export class AppComponent {
  message: string = 'loading...'

  ngAfterViewInit() {
    setTimeout(() => {
      this.message = 'all done loading'
    })
  }
}

It is not the cleanest solution, but it will also work.

One More Example

Now I want to show you exactly the same error but with different code.

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, CommonModule],
  template: ` <div>I'm {{ loading$ | async }} {{ response$ | async }}</div> `,
  styleUrl: './app.component.css',
})
export class AppComponent {
  loading$ = new BehaviorSubject(true);

  response$ = of('response').pipe(
    tap(() => this.loading$.next(false)),
  );

}

The logic is similar, but here we use RxJS and async pipes. We have a loading$ stream and a response$ stream. The problem here is that immediately after setting the response, we change the loading state.

error 2

The error is the same, but the values change from true to false.

To fix this error, we must apply the same fix as we did with setTimeout, but in an RxJS way.

response$ = of('response').pipe(
  delay(0),
  tap(() => this.loading$.next(false)),
);

Now our tap will be asynchronous, which eliminates the problem.

Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!

📚 Source code of what we've done
Don't miss a thing!
Follow me on Youtube, Twitter or Instagram.
Oleksandr Kocherhin
Oleksandr Kocherhin is a full-stack developer with a passion for learning and sharing knowledge on Monsterlessons Academy and on his YouTube channel. With around 15 years of programming experience and nearly 9 years of teaching, he has a deep understanding of both disciplines. He believes in learning by doing, a philosophy that is reflected in every course he teaches. He loves exploring new web and mobile technologies, and his courses are designed to give students an edge in the fast-moving tech industry.