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:

bash
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 ngAfterViewInit after Angular has checked the view
  • Child component emits an event in ngOnInit that changes a parent's bound property
  • Async data arriving synchronously during the same tick as change detection
  • Using setTimeout with 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. 1.**Use ngAfterContentInit instead of ngAfterViewInit** for data that affects bindings:
  2. 2.```typescript
  3. 3.// BAD: modifies bound property after view check
  4. 4.ngAfterViewInit() {
  5. 5.this.title = 'Updated Title';
  6. 6.}

// GOOD: modifies before view check completes ngOnInit() { this.title = 'Updated Title'; } ```

  1. 1.Use ChangeDetectorRef to trigger a new detection cycle:
  2. 2.```typescript
  3. 3.import { ChangeDetectorRef } from '@angular/core';

constructor(private cdr: ChangeDetectorRef) {}

ngAfterViewInit() { this.loadData(); this.cdr.detectChanges(); // Runs change detection again for this component } ```

  1. 1.Use async pipe to handle data that arrives after initial rendering:
  2. 2.```typescript
  3. 3.// Component
  4. 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. 1.**Use OnPush change detection** to control when detection runs:
  2. 2.```typescript
  3. 3.@Component({
  4. 4.selector: 'app-child',
  5. 5.changeDetection: ChangeDetectionStrategy.OnPush,
  6. 6.template: <p>{{ data }}</p>,
  7. 7.})
  8. 8.export class ChildComponent {
  9. 9.@Input() data: string;
  10. 10.}
  11. 11.`
  12. 12.For child-to-parent communication, emit after the parent has finished checking:
  13. 13.```typescript
  14. 14.// Child component
  15. 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 in ngAfterViewInit
  • Use the async pipe for data that may change after initial rendering
  • Run ng build --configuration=production regularly to catch dev-mode-only errors
  • Use ChangeDetectorRef.detectChanges() deliberately, not as a catch-all fix
  • Prefer OnPush change 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