Angular: ExpressionChangedAfterItHasBeenCheckedError β Detailed Explanation & Solutions
Introduction
The ExpressionChangedAfterItHasBeenCheckedError is one of the most common errors encountered in Angular applications. This error occurs when Angular detects that a property has changed after the change detection cycle has already run. It is a safeguard built into Angular to prevent unintended side effects and ensure predictable application behavior.
Understanding the Error
When Does This Error Occur?
This error typically arises when:
- A component modifies a bound property inside a lifecycle hook (such as
ngOnInit
orngAfterViewInit
), causing a change after Angular has already checked the component. - A value changes asynchronously (e.g., using
setTimeout
, Promises, Observables) but is still within the same change detection cycle. - Parent and child components are out of sync because of timing issues with data binding.
Example Scenario
Consider a simple example where a component updates a property in ngOnInit
:
@Component({
selector: 'app-root',
template: `<h1>{{ title }}</h1>`
})
export class AppComponent {
title: string = 'Angular App';
ngOnInit() {
this.title = 'Updated Title';
}
}
β
This works fine because title
is updated before Angular finishes the change detection cycle.
However, if we introduce a setTimeout()
inside ngOnInit
:
ngOnInit() {
setTimeout(() => {
this.title = 'Updated Title';
}, 0);
}
π¨ This will throw ExpressionChangedAfterItHasBeenCheckedError
because the update happens after the initial change detection.
Why Does This Happen?
Angular runs a change detection cycle to update the DOM based on component state. The steps are as follows:
- Component Initialization
- The component is created, and the constructor is called.
- Lifecycle Hooks Execution
ngOnInit()
runs, allowing initial data modifications.
- Change Detection Runs
- Angular checks for changes and updates the DOM.
- View Rendering
- The updated values are reflected in the UI.
- Change Detection Runs Again (For Debug Mode)
- In development mode, Angular runs a second check to catch unintended modifications.
- Error Thrown If Values Have Changed
- If Angular detects a change between the first and second checks, it throws
ExpressionChangedAfterItHasBeenCheckedError
.
- If Angular detects a change between the first and second checks, it throws
How to Fix This Error?
There are several ways to fix this issue, depending on the cause.
1. Use ChangeDetectorRef.detectChanges()
If the value is changing after the initial check, manually trigger change detection using ChangeDetectorRef
:
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
template: `<h1>{{ title }}</h1>`
})
export class AppComponent implements OnInit {
title: string = 'Angular App';
constructor(private cdRef: ChangeDetectorRef) {}
ngOnInit() {
setTimeout(() => {
this.title = 'Updated Title';
this.cdRef.detectChanges(); // β
Manually trigger change detection
}, 0);
}
}
β This forces Angular to re-check the changes and update the DOM properly.
2. Use setTimeout()
Correctly
setTimeout()
moves the execution to a new event loop cycle, ensuring Angular doesnβt detect changes in the same cycle.
ngOnInit() {
setTimeout(() => {
this.title = 'Updated Title';
});
}
β This ensures the update happens in the next change detection cycle.
3. Use AfterViewInit
Instead of OnInit
If you are modifying values that depend on the view (DOM elements, child components, etc.), use ngAfterViewInit
instead of ngOnInit
:
ngAfterViewInit() {
this.title = 'Updated Title';
}
β This ensures updates happen after the view has been initialized.
π¨ Caution: Do not modify properties bound to the template inside ngAfterViewInit
, as it may still trigger the error. Use setTimeout()
or ChangeDetectorRef
instead.
4. Mark Component for Check Using markForCheck()
If your component uses OnPush change detection strategy, use markForCheck()
to manually mark it as changed.
import { ChangeDetectorRef, ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `<h1>{{ title }}</h1>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
title: string = 'Angular App';
constructor(private cdRef: ChangeDetectorRef) {}
ngOnInit() {
setTimeout(() => {
this.title = 'Updated Title';
this.cdRef.markForCheck(); // β
Marks component for change detection
}, 0);
}
}
β This marks the component for change detection in the next cycle.
5. Ensure Parent and Child Components Are in Sync
If the error occurs due to a child component updating a property that the parent is bound to, use ngOnChanges()
in the child:
@Input() data: string;
ngOnChanges() {
console.log('Data changed:', this.data);
}
β This ensures Angular correctly tracks changes between parent and child components.
Best Practices to Avoid This Error
βοΈ Do not modify component properties inside ngOnInit()
asynchronously.
βοΈ Use setTimeout()
or Promise.resolve()
to defer execution.
βοΈ Use ChangeDetectorRef.detectChanges()
if needed.
βοΈ Use AfterViewInit
instead of OnInit
for DOM-dependent updates.
βοΈ Ensure parent-child bindings are handled properly using ngOnChanges()
.
βοΈ Use ChangeDetectionStrategy.OnPush
with markForCheck()
for performance optimization.