Introduction
The ExpressionChangedAfterItHasBeenCheckedError is one of the most common Angular errors in development mode. It occurs when a component changes a bound value after Angular's change detection has already checked it during the current detection cycle:
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed
after it was checked. Previous value: 'title: Hello'. Current value: 'title: Hello World'.This error only appears in development mode (Angular runs a second change detection pass to detect such issues) and prevents the application from behaving correctly.
Symptoms
- Console error: "ExpressionChangedAfterItHasBeenCheckedError" in development mode
- Production build works but displays incorrect or stale data
- Error occurs after modifying a component property in
ngAfterViewInit - Parent component binding shows old value while child shows new value
- Error disappears when
enableProdMode()is called but the data inconsistency remains
Common Causes
- Modifying a bound property in
ngAfterViewInitafter Angular has checked the view - Child component emits an event in
ngOnInitthat changes a parent's bound property - Async data arriving synchronously during the same tick as change detection
- Using
setTimeoutwith 0ms delay as a workaround instead of fixing the root cause - Parent and child components modifying the same bound property in the same cycle
Step-by-Step Fix
- 1.**Use
ngAfterContentInitinstead ofngAfterViewInit** for data that affects bindings: - 2.```typescript
- 3.// BAD: modifies bound property after view check
- 4.ngAfterViewInit() {
- 5.this.title = 'Updated Title';
- 6.}
// GOOD: modifies before view check completes ngOnInit() { this.title = 'Updated Title'; } ```
- 1.Use ChangeDetectorRef to trigger a new detection cycle:
- 2.```typescript
- 3.import { ChangeDetectorRef } from '@angular/core';
constructor(private cdr: ChangeDetectorRef) {}
ngAfterViewInit() { this.loadData(); this.cdr.detectChanges(); // Runs change detection again for this component } ```
- 1.Use async pipe to handle data that arrives after initial rendering:
- 2.```typescript
- 3.// Component
- 4.title$ = new BehaviorSubject<string>('Loading...');
ngOnInit() { this.title$.next('Loaded Title'); }
// Template <h1>{{ title$ | async }}</h1> ``` The async pipe handles change detection automatically when new values arrive.
- 1.**Use
OnPushchange detection** to control when detection runs: - 2.```typescript
- 3.@Component({
- 4.selector: 'app-child',
- 5.changeDetection: ChangeDetectionStrategy.OnPush,
- 6.template:
<p>{{ data }}</p>, - 7.})
- 8.export class ChildComponent {
- 9.@Input() data: string;
- 10.}
- 11.
` - 12.For child-to-parent communication, emit after the parent has finished checking:
- 13.```typescript
- 14.// Child component
- 15.@Output() initialized = new EventEmitter<string>();
ngAfterViewInit() { // Use setTimeout to defer emission to next tick setTimeout(() => { this.initialized.emit('child data'); }); } ```
Prevention
- Never modify
@Input()or bound properties inngAfterViewInit - Use the async pipe for data that may change after initial rendering
- Run
ng build --configuration=productionregularly to catch dev-mode-only errors - Use
ChangeDetectorRef.detectChanges()deliberately, not as a catch-all fix - Prefer
OnPushchange detection for components that receive data via@Input() - Add unit tests that verify component properties are set during
ngOnInit - Use Angular's strict mode (
ng new --strict) which enforces better practices from the start