← Back to list

“ExpressionChangedAfterItHasBeenCheckedError” — Explained and Fixed

ExpressionChangedAfterItHasBeenCheckedError is , without a doubt, one of the most commonly faced error for new Angular developers & can’t…

Pawan Kumawat in Level Up Coding · 2026-04-03 06:42 · 32 claps · 4.0 min read paywalled
#angular #frontend #javascript #angular2 #programming
Open on Medium ↗
Wiki topics: 💻 · Programming 🌐 · Web Development 📰 · Journalism & News

**ExpressionChangedAfterItHasBeenCheckedError**” — Explained and Fixed

ExpressionChangedAfterItHasBeenCheckedError is, without a doubt, one of the most commonly faced error for new Angular developers & can’t be fixed without googling. Knowing why it happens helps you fix it easily and build stable Angular apps.

ExpressionChangedAfterItHasBeenCheckedError

ExpressionChangedAfterItHasBeenCheckedError

Angular’s dev-mode change detection runs twice on purpose. When your template reads a value that you mutate between those passes, Angular throws ExpressionChangedAfterItHasBeenCheckedError. Here is why it happens and several practical fixes.

🎯 Why this error exists

In development, Angular runs change detection twice in a row for the same turn. The second pass checks that nothing “moved” after the first — a guard against unstable bindings and hard-to-debug UI flicker.

👉***How changedetection works in Angular — video***

If the first pass sees value A and the second pass sees B for the same binding, Angular assumes something updated the model during change detection (often from a lifecycle hook), which is unsafe. That is when you get ExpressionChangedAfterItHasBeenCheckedError.

> Note: This double-check is a development behavior. Production builds do not use the same strict two-pass check, but fixing the underlying pattern still matters: mutating state during CD can cause subtle bugs.

⚡ Use case: updating UI from ngAfterViewInit

A classic trigger is changing a property that the template displays inside ngAfterViewInit— after the view exists but while change detection is still settling.

import { Component, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-demo',
  template: `<p>{{ message }}</p>`,
})
export class DemoComponent implements AfterViewInit {
  message = 'first';

  ngAfterViewInit(): void {
    this.message = 'updated in afterViewInit'; 
   // often triggers the error in dev
  }
}

The child view is ready, but flipping message here can make the value differ between Angular’s two dev-mode checks.

🏛️ Main idea: defer the update or control detection

You need the model to not change between those two reads, or you need to run your update outside the current synchronous change-detection cycle (next macrotask/microtask), or you opt into OnPushand trigger detection explicitly when you are ready.

1️⃣ Step 1: ngAfterContentChecked (earlier in the lifecycle)

Sometimes you can move work to ngAfterContentChecked, which runs before ngAfterViewInitin the lifecycle order, so the value stabilizes before the view finishes initializing. This is not a universal fix — it depends on what you are updating — but it matches the pattern from the short: adjust state before the phase where the error appears.

import { Component, AfterContentChecked } from '@angular/core';

@Component({
  selector: 'app-demo',
  template: `<p>{{ message }}</p>`,
})
export class DemoComponent implements AfterContentChecked {
  message = 'ready';

  ngAfterContentChecked(): void {
    // Only if this fits your flow — avoid heavy work here (runs often)
    if (this.message === 'ready') {
      this.message = 'from content checked';
    }
  }
}

Use this carefully: ngAfterContentCheckedruns very frequently; guard your logic so you do not cause infinite loops or performance issues.

2️⃣ Step 2: Defer with setTimeout or a Promise

Pushing the update to the next macrotask (or a microtask) lets the current change-detection cycle finish with a stable value; the update runs afterward.

ngAfterViewInit(): void {
  setTimeout(() => {
    this.message = 'deferred with setTimeout';
  }, 0);
}
ngAfterViewInit(): void {
  Promise.resolve().then(() => {
    this.message = 'deferred with Promise';
  });
}

This is a common, pragmatic fix for one-off UI tweaks after the view is up.

👉***fix settimeout***

3️⃣ Step 3: ChangeDetectionStrategy.OnPush + detectChanges()

With OnPush, you take responsibility for when the view updates. After you mutate state intentionally, you can call ChangeDetectorRef.detectChanges() (or markForCheck() in the right context) so Angular reconciles the view in a controlled way.

import {
  Component,
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
} from '@angular/core';

@Component({
  selector: 'app-demo',
  template: `<p>{{ message }}</p>`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DemoComponent implements AfterViewInit {
  message = 'first';

  constructor(private cdr: ChangeDetectorRef) {}

  ngAfterViewInit(): void {
    this.message = 'updated with OnPush';
    this.cdr.detectChanges();
  }
}

Prefer markForCheck() when you are integrating with async inputs or parent-driven updates; use detectChanges() when you need this component’s tree updated immediately after a local change.

4️⃣ Step 4: RxJS — delay(0) for “same tick” updates

If a stream synchronously emits a value that alters the template during change detection, you can see the same class of problem. delay(0) (scheduler default) effectively schedules work on a timer — similar in spirit to setTimeout(…, 0) — so the emission happens after the current synchronous turn.

import { delay } from 'rxjs/operators';

this.data$ = this.api.getData().pipe(
  delay(0),
  // ... further operators
);

Use this when the observable fires too “early” relative to the template’s read cycle, not as a blanket pattern for every stream.

📝 Summary

  • ExpressionChangedAfterItHasBeenCheckedErrorin dev usually means a binding’s value changed between Angular’s two development change-detection passes.
  • ngAfterViewInitis a frequent culprit when it mutates template-bound state; the view is live while CD is still strict.
  • Mitigations include moving work to ngAfterContentChecked(with guards), deferring with setTimeout(…, 0) or Promise.resolve().then(…), using OnPushwith ChangeDetectorRef.detectChanges() / markForCheck(), and delay(0) on RxJS when emissions land in the wrong synchronous slice of CD.
  • Treat fixes as design choices: deferral is simple; OnPushscales better for larger apps but needs a consistent strategy.

📌 Follow & Connect

👉 My Udemy Courses: *Angular Practicals *👉 My Udemy Courses: Design Patterns in Angular 👉 My Udemy Profile 👉 LinkedIn 👉 Medium 👉 YouTube 👉 Website

I’ve published two Angular courses on Udemy — **Angular Practicals for hands-on problems and solutions, and [Design Patterns in Angular — Practical Guide](https://www.udemy.com/course/design-patterns-in-angular-practical-guide/?couponCode=APR_2026)** for patterns in real apps. Both are often available at a discount.

Happy Coding !!


메타데이터
post_id
a5bc31c4432f
slug
expressionchangedafterithasbeencheckederror-explained-and-fixed-a5bc31c4432f
url
https://medium.com/@pawan-kumawat/expressionchangedafterithasbeencheckederror-explained-and-fixed-a5bc31c4432f
canonical_url
https://medium.com/@pawan-kumawat/expressionchangedafterithasbeencheckederror-explained-and-fixed-a5bc31c4432f
author_url
https://medium.com/@pawan-kumawat
status
ok
fetched_at
2026-06-14 11:28:49