Fix: Angular ExpressionChangedAfterItHasBeenCheckedError
Part of: React & Frontend Errors
Quick Answer
How to fix ExpressionChangedAfterItHasBeenCheckedError in Angular caused by change detection timing issues, lifecycle hooks, async pipes, and parent-child data flow.
The Error
You run your Angular application in development mode and see this in the console:
ERROR Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError:
Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.Or variations like:
ExpressionChangedAfterItHasBeenCheckedError:
Expression has changed after it was checked. Previous value for 'ngIf: false'. Current value: 'ngIf: true'.ExpressionChangedAfterItHasBeenCheckedError:
Expression has changed after it was checked. Previous value: 'null'. Current value: '[object Object]'.ExpressionChangedAfterItHasBeenCheckedError:
Expression has changed after it was checked. Previous value for 'class.active: false'. Current value: 'class.active: true'.The error only appears in development mode. In production, Angular skips the second check cycle that detects this mismatch. That does not mean the problem is harmless — it means your data is changing at the wrong time, which can lead to visual glitches, stale values, and hard-to-trace bugs.
Why This Happens
Angular’s change detection runs in a unidirectional data flow. During each change detection cycle, Angular checks every bound expression in your templates to see if values have changed. In development mode, Angular runs this check twice. If a value changes between the first and second check, Angular throws ExpressionChangedAfterItHasBeenCheckedError.
Here is the sequence:
- Angular triggers change detection (e.g., after an event, HTTP response, or timer).
- Angular checks all template bindings and updates the DOM.
- In dev mode only, Angular runs a second check to verify stability.
- If any binding returns a different value during this second check, the error is thrown.
The root cause is always the same: something modifies a bound value after Angular has already checked it during the current cycle. This typically happens when:
- A child component modifies parent data during initialization or lifecycle hooks.
- A lifecycle hook like
ngAfterViewInitchanges a bound property after Angular has finished checking the view. - Getter methods or pipes return new object references on every call.
- Async operations complete and update data at the wrong time in the change detection cycle.
- A shared service emits a new value during change detection.
Understanding the change detection cycle is key. Angular checks the parent component first, then its children. If a child changes something that affects the parent’s template bindings during its own initialization, the parent’s bindings become stale — and the second verification check catches this inconsistency.
Common Mistake: Many developers suppress this error with
setTimeoutordetectChanges()without understanding the root cause. These are valid solutions in specific cases, but blindly applying them can mask real architectural problems. Always understand why the value is changing before choosing a fix.
Diagnostic Timeline
The first instinct is to sprinkle ChangeDetectorRef.detectChanges() until the error stops. That hides the problem instead of fixing it. Walk this timeline first — most cases resolve within five minutes.
Minute 0: Read the error verbatim. The full message names the changed value:
ExpressionChangedAfterItHasBeenCheckedError:
Expression has changed after it was checked.
Previous value: 'false'. Current value: 'true'.Those two values are your only clue. A 'false' -> 'true' swap points to a boolean flag flipping during a lifecycle hook. A null -> [object Object] swap points to async data assignment. A 'ngIf: false' -> 'ngIf: true' is a structural directive driven by a property that just mutated.
Minute 1: Confirm it only fires in dev mode. Angular runs change detection twice in development to catch this mismatch. The error never appears in production — but the underlying bug is still there. If the error magically disappeared after a ng build --configuration=production, you suppressed the alarm, not the bug.
Minute 2: Identify the offending property via the stack trace. Open DevTools, expand the error stack, and find the first frame inside your own components. The template binding above that frame is the one whose value changed. In Angular 9+, the stack trace includes the template line number.
Minute 3: Locate the mutation site. Search for any code that writes to that property:
grep -rn "this\.\(isLoaded\|hasContent\|isFormValid\)\s*=" src/Then ask which lifecycle hook the mutation runs in. The hook tells you the cause:
constructoror top-level field initializer → too early, move tongOnInitngOnInitwith a synchronous subject (BehaviorSubject,of()) → emits during parent’s checkngAfterContentInit→ usually safe, but check if the change propagates upwardngAfterViewInit→ too late, parent already checked; needsmarkForCheckorsetTimeout- A getter or pipe (look for
get someProp()returning a new array/object every call) → recompute on every check
Minute 5: Rule out side-effecting getters and pipes. This is the most-missed cause. Add a console.count('getter') inside any suspicious getter. If it fires more than twice per cycle, you have a referential-equality problem — every call returns a new object reference, so Angular’s second pass sees a different value:
get visibleItems() {
console.count('visibleItems'); // Should fire at most twice
return this.items.filter(i => i.visible); // New array each call
}Minute 7: Check parent-child data flow. If the change happens inside a child component but the binding is on the parent, the child violated unidirectional data flow. Open the child’s ngOnInit/ngAfterViewInit and look for any mutation of a @Input() value or a shared service property that the parent reads.
Minute 10: Only now reach for setTimeout or detectChanges(). If the timeline above identified an unavoidable post-check write (e.g., reading @ViewChild properties), Fix 2 and Fix 3 are the durable answers — but you should reach them with a hypothesis, not as a first move.
Fix 1: Move Logic to ngOnInit Instead of the Constructor
If you set or modify bound properties in the constructor, the timing can conflict with change detection. The constructor runs before Angular has initialized the component’s bindings.
Broken — setting state in the constructor:
@Component({
selector: 'app-user-profile',
template: `<div *ngIf="isLoaded">{{ userName }}</div>`
})
export class UserProfileComponent {
isLoaded = false;
userName = '';
constructor(private userService: UserService) {
this.userService.getUser().subscribe(user => {
this.userName = user.name;
this.isLoaded = true; // Changes value during parent's check cycle
});
}
}Fixed — move to ngOnInit:
@Component({
selector: 'app-user-profile',
template: `<div *ngIf="isLoaded">{{ userName }}</div>`
})
export class UserProfileComponent implements OnInit {
isLoaded = false;
userName = '';
constructor(private userService: UserService) {}
ngOnInit(): void {
this.userService.getUser().subscribe(user => {
this.userName = user.name;
this.isLoaded = true;
});
}
}ngOnInit runs after Angular has set up the component’s input bindings, making it the proper place for initialization logic. The constructor should only be used for dependency injection.
This pattern applies broadly — any data fetching, subscription setup, or state initialization belongs in ngOnInit, not in the constructor. If you are working with TypeScript components and Angular, this lifecycle distinction is critical.
Fix 2: Use ChangeDetectorRef.detectChanges()
When you genuinely need to update a bound value after Angular has checked it — for example, after a ViewChild becomes available — you can explicitly tell Angular to re-run change detection.
Broken — modifying state in ngAfterViewInit:
@Component({
selector: 'app-tabs',
template: `
<div [class.has-content]="hasContent">
<ng-content></ng-content>
</div>
`
})
export class TabsComponent implements AfterViewInit {
hasContent = false;
@ContentChildren(TabComponent) tabs!: QueryList<TabComponent>;
ngAfterViewInit(): void {
this.hasContent = this.tabs.length > 0; // Error: value changed after check
}
}Fixed — trigger change detection after the update:
import { ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'app-tabs',
template: `
<div [class.has-content]="hasContent">
<ng-content></ng-content>
</div>
`
})
export class TabsComponent implements AfterViewInit {
hasContent = false;
@ContentChildren(TabComponent) tabs!: QueryList<TabComponent>;
constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit(): void {
this.hasContent = this.tabs.length > 0;
this.cdr.detectChanges(); // Tells Angular to re-check this component
}
}detectChanges() triggers a synchronous change detection run for this component and its children. Angular performs a fresh check, so the second verification pass sees the already-updated value and does not throw.
Note: Use detectChanges() sparingly. If you find yourself calling it in many places, it often points to an architectural issue — consider restructuring your data flow instead.
Fix 3: Use setTimeout to Defer Updates
Wrapping the problematic update in setTimeout pushes it to the next macrotask, which triggers a new change detection cycle. The update no longer conflicts with the current check.
@Component({
selector: 'app-dashboard',
template: `<p>Status: {{ status }}</p>`
})
export class DashboardComponent implements AfterViewInit {
status = 'loading';
ngAfterViewInit(): void {
setTimeout(() => {
this.status = 'ready'; // Runs in a new change detection cycle
});
}
}The setTimeout with no delay (or Promise.resolve().then(...)) defers execution until after the current change detection cycle completes. Angular’s zone.js patches setTimeout, so it automatically triggers a new round of change detection.
When to use this approach:
- When
detectChanges()causes cascading issues. - When you need to update a value after content projection or view initialization.
- When dealing with third-party libraries that modify DOM outside Angular’s awareness.
This technique is well-known in other frameworks too. React developers face similar timing challenges — see preventing render-time state updates for the comparable pattern.
Fix 4: Fix Async Pipe Causing the Error
The async pipe is generally the recommended way to handle observables in templates. But it can trigger this error when the observable emits synchronously during change detection.
Broken — observable emits immediately on subscription:
@Component({
selector: 'app-notification',
template: `<div *ngIf="message$ | async as msg">{{ msg }}</div>`
})
export class NotificationComponent implements OnInit {
message$!: Observable<string>;
constructor(private notificationService: NotificationService) {}
ngOnInit(): void {
// BehaviorSubject emits its current value immediately
// If parent changes this during its own check, error occurs
this.message$ = this.notificationService.latestMessage$;
}
}Fixed — use shareReplay and ensure stable emission timing:
@Component({
selector: 'app-notification',
template: `<div *ngIf="message$ | async as msg">{{ msg }}</div>`
})
export class NotificationComponent implements OnInit {
message$!: Observable<string>;
constructor(private notificationService: NotificationService) {}
ngOnInit(): void {
this.message$ = this.notificationService.latestMessage$.pipe(
observeOn(asyncScheduler), // Delays emission to next microtask
shareReplay(1)
);
}
}The observeOn(asyncScheduler) operator defers emission so it does not happen synchronously during change detection. Alternatively, if the observable comes from an HTTP call (which is inherently async), the error usually does not occur — the problem is specific to synchronous emissions like BehaviorSubject, of(), or startWith().
Another common cause: initializing the observable in ngAfterViewInit instead of ngOnInit. Always set up your observable references as early as possible — ideally in ngOnInit or at the field declaration level.
Fix 5: Fix Parent-Child Component Data Flow
This is one of the most common causes. A child component modifies a parent’s data during its own initialization, violating Angular’s unidirectional data flow.
Broken — child modifies parent state via a service:
// parent.component.ts
@Component({
selector: 'app-parent',
template: `
<p>Count: {{ sharedService.count }}</p>
<app-child></app-child>
`
})
export class ParentComponent {
constructor(public sharedService: SharedService) {}
}
// child.component.ts
@Component({
selector: 'app-child',
template: `<p>Child loaded</p>`
})
export class ChildComponent implements OnInit {
constructor(private sharedService: SharedService) {}
ngOnInit(): void {
this.sharedService.count = 42; // Modifies parent's binding after parent was checked
}
}Fixed — use an event-based approach with proper timing:
// child.component.ts
@Component({
selector: 'app-child',
template: `<p>Child loaded</p>`
})
export class ChildComponent implements OnInit {
@Output() initialized = new EventEmitter<number>();
ngOnInit(): void {
// Emit event instead of directly modifying shared state
Promise.resolve().then(() => {
this.initialized.emit(42);
});
}
}
// parent.component.ts
@Component({
selector: 'app-parent',
template: `
<p>Count: {{ count }}</p>
<app-child (initialized)="onChildInit($event)"></app-child>
`
})
export class ParentComponent {
count = 0;
onChildInit(value: number): void {
this.count = value;
}
}The key principle: data flows down from parent to child via @Input(), and events flow up from child to parent via @Output(). When a child needs to change something the parent displays, it should emit an event, not directly mutate shared state.
If you have worked with React, this is the same principle behind avoiding too many re-renders — state updates must happen at the right time and in the right direction.
Fix 6: Use OnPush Change Detection Strategy
Switching to OnPush change detection reduces the surface area for this error. With OnPush, Angular only checks the component when its @Input() references change, an event fires from the template, or you manually trigger detection.
@Component({
selector: 'app-performance-list',
template: `
<ul>
<li *ngFor="let item of items">{{ item.name }}</li>
</ul>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PerformanceListComponent {
@Input() items: Item[] = [];
}With OnPush, Angular will not re-check this component on every change detection cycle — only when items receives a new reference. This means accidental mutations to the same array reference will not trigger the error (or updates, which is the trade-off).
To update an OnPush component from within, use ChangeDetectorRef.markForCheck():
@Component({
selector: 'app-live-feed',
template: `<p>{{ latestUpdate }}</p>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class LiveFeedComponent implements OnInit, OnDestroy {
latestUpdate = '';
private destroy$ = new Subject<void>();
constructor(
private feedService: FeedService,
private cdr: ChangeDetectorRef
) {}
ngOnInit(): void {
this.feedService.updates$
.pipe(takeUntil(this.destroy$))
.subscribe(update => {
this.latestUpdate = update;
this.cdr.markForCheck(); // Schedules a check for the next cycle
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}markForCheck() is different from detectChanges(). It does not trigger an immediate check — it marks the component (and its ancestors) as dirty, so Angular will check it during the next change detection cycle. This avoids the timing conflict that causes ExpressionChangedAfterItHasBeenCheckedError.
Pro Tip:
OnPushis not just a fix for this error — it is a performance best practice. Components that only depend on their inputs and internal events should always useOnPush. It reduces unnecessary change detection cycles across your entire app and prevents a whole category of timing bugs. Start new components withOnPushby default and only switch toDefaultwhen you have a specific reason.
Fix 7: Fix ngAfterViewInit vs ngAfterContentInit Timing
These two lifecycle hooks run at different points, and using the wrong one is a frequent cause of this error.
ngAfterContentInitruns after Angular projects content into the component (via<ng-content>). It runs before the parent’s change detection check completes.ngAfterViewInitruns after Angular initializes the component’s view and child views. It runs after the parent’s change detection check completes.
If you modify a bound property in ngAfterViewInit, the parent has already been checked — so the error fires. But the same modification in ngAfterContentInit may work fine.
Broken — updating state in ngAfterViewInit:
@Component({
selector: 'app-form-wrapper',
template: `
<div [class.valid]="isFormValid">
<app-dynamic-form #form></app-dynamic-form>
</div>
`
})
export class FormWrapperComponent implements AfterViewInit {
isFormValid = false;
@ViewChild('form') formComponent!: DynamicFormComponent;
ngAfterViewInit(): void {
this.isFormValid = this.formComponent.form.valid; // Too late — parent already checked
}
}Fixed — subscribe to changes instead of reading synchronously:
@Component({
selector: 'app-form-wrapper',
template: `
<div [class.valid]="isFormValid">
<app-dynamic-form #form></app-dynamic-form>
</div>
`
})
export class FormWrapperComponent implements AfterViewInit {
isFormValid = false;
@ViewChild('form') formComponent!: DynamicFormComponent;
constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit(): void {
// Subscribe to form status changes — each emission triggers a new cycle
this.formComponent.form.statusChanges.subscribe(status => {
this.isFormValid = status === 'VALID';
this.cdr.markForCheck();
});
// Handle the initial state
this.isFormValid = this.formComponent.form.valid;
this.cdr.detectChanges();
}
}When you need to read from @ViewChild references, you have no choice but to use ngAfterViewInit. The key is to follow up with detectChanges() for the initial value, then use reactive patterns (subscriptions, observables) for subsequent updates.
If your logic only depends on projected content (not view children), move it to ngAfterContentInit instead — it runs earlier and avoids the issue entirely.
Fix 8: Use BehaviorSubject for Reactive Data Flow
Getters and computed properties are a hidden source of this error. If a getter returns a new object or array reference on every call, Angular sees a different value on each check.
Broken — getter creates a new array every time:
@Component({
selector: 'app-filtered-list',
template: `
<ul>
<li *ngFor="let item of activeItems">{{ item.name }}</li>
</ul>
`
})
export class FilteredListComponent {
items: Item[] = [];
get activeItems(): Item[] {
return this.items.filter(i => i.active); // New array reference every call
}
}Fixed — use a BehaviorSubject to manage the derived state:
@Component({
selector: 'app-filtered-list',
template: `
<ul>
<li *ngFor="let item of activeItems$ | async">{{ item.name }}</li>
</ul>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FilteredListComponent implements OnInit {
private itemsSubject = new BehaviorSubject<Item[]>([]);
activeItems$!: Observable<Item[]>;
ngOnInit(): void {
this.activeItems$ = this.itemsSubject.pipe(
map(items => items.filter(i => i.active)),
distinctUntilChanged((prev, curr) =>
prev.length === curr.length && prev.every((item, i) => item === curr[i])
)
);
}
updateItems(newItems: Item[]): void {
this.itemsSubject.next(newItems);
}
}The BehaviorSubject combined with distinctUntilChanged ensures Angular only receives a new reference when the data actually changes. The async pipe handles subscription and unsubscription automatically.
This pattern scales well. For complex state management, consider a dedicated state management library like NgRx or Akita, which enforce immutable data patterns and eliminate this class of errors entirely.
You can also fix the simpler cases without a BehaviorSubject by caching the computed value:
export class FilteredListComponent implements OnChanges {
@Input() items: Item[] = [];
activeItems: Item[] = [];
ngOnChanges(): void {
this.activeItems = this.items.filter(i => i.active); // Computed once per change, not per check
}
}This approach works well when the data comes from an @Input(). The ngOnChanges hook runs once when the input changes, producing a stable reference that Angular can check safely. The same principle applies to any derived state: compute once when the source changes, not on every read.
Still Not Working?
If none of the fixes above solved your issue, check these less common causes:
Getter with Date or Math.random():
// This will ALWAYS trigger the error
get timestamp(): string {
return new Date().toISOString(); // Different value every millisecond
}Cache the value instead:
timestamp: string = new Date().toISOString();Third-party library modifying the DOM outside Angular:
Some libraries (jQuery plugins, D3, chart libraries) modify the DOM directly. Wrap their callbacks in NgZone.run():
constructor(private ngZone: NgZone) {}
ngAfterViewInit(): void {
this.chart = new ThirdPartyChart(this.chartEl.nativeElement);
this.chart.onReady(() => {
this.ngZone.run(() => {
this.isChartReady = true;
});
});
}Running outside Angular’s zone accidentally:
If code runs outside NgZone (via runOutsideAngular), changes will not trigger change detection. When you later re-enter the zone, the accumulated changes can cause the error. Make sure zone transitions are intentional and managed.
Structural directives with dynamic conditions:
Using *ngIf or *ngSwitch with expressions that evaluate differently during the verification check will trigger the error. Ensure the condition resolves to a stable value before change detection runs.
Debug technique — find the exact binding:
The error message tells you the previous and current values. To find which binding caused it, add ng.getComponent(element) in the browser console and inspect the component’s properties. In Angular 9+, enable detailed error messages:
// main.ts (development only)
import { enableProdMode } from '@angular/core';
// Do NOT call enableProdMode() during debugging — the error
// only shows in dev mode and it is your best diagnostic tool.Check the full stack trace in the console. It shows the exact template expression and component where the mismatch occurred. Follow the chain from the point of change back to the source of the mutation.
Check for @HostBinding properties driven by getters. A @HostBinding('class.active') that calls a getter behaves the same as a template getter — it runs on every check. If the getter computes from time, randomness, or freshly filtered arrays, you get the error on every cycle. Cache the value on a field and update it only when the source changes.
Look for markAsDirty / markAsTouched called during view init. Reactive Forms validators that call control.markAsTouched() or control.updateValueAndValidity() inside ngAfterViewInit push a status change after the parent has been checked. Wrap the call in Promise.resolve().then(...) or move it into a valueChanges subscription so it runs in a new cycle.
Inspect content-projected components. If you have <ng-content> and the projected component sets a property the host reads via @ContentChild, the read happens in ngAfterContentChecked — which fires before the host’s view check. The fix is to subscribe to the child’s EventEmitter instead of reading the property synchronously.
Rule out Angular Signals interop. In Angular 16+, mixing Signals with zone-based change detection can trip this error when a signal updates inside effect() that runs during a check. Move signal writes out of effect() callbacks that depend on bound state, or convert the consuming component to fully signal-based with OnPush and toSignal.
Audit your *ngIf and *ngFor conditions. Conditions that reference Date.now(), Math.random(), or any external mutable singleton will return a different value on the verification pass. Make the condition reference only fields and inputs that change through Angular’s normal flow.
If you are dealing with other Angular injection issues alongside this error, check how to fix NullInjectorError for related dependency injection problems that can compound with change detection timing issues.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Analog Not Working — Routes Not Loading, API Endpoints Failing, or Vite Build Errors
How to fix Analog (Angular meta-framework) issues — file-based routing, API routes with Nitro, content collections, server-side rendering, markdown pages, and deployment.
Fix: Angular SSR Not Working — Hydration Failing, Window Not Defined, or Build Errors
How to fix Angular Server-Side Rendering issues — @angular/ssr setup, hydration, platform detection, transfer state, route-level rendering, and deployment configuration.
Fix: RxJS Not Working — Observable Not Emitting, Memory Leak from Unsubscribed Stream, or Operator Behaving Unexpectedly
How to fix RxJS issues — subscription management, switchMap vs mergeMap vs concatMap, error handling with catchError, Subject types, cold vs hot observables, and Angular async pipe.
Fix: Angular Pipe Not Working — Custom Pipe Not Transforming or async Pipe Not Rendering
How to fix Angular pipe issues — declaring pipes in modules, standalone pipe imports, pure vs impure pipes, async pipe with observables, pipe chaining, and custom pipe debugging.