The Problem

The ExpressionChangedAfterItHasBeenCheckedError (NG0100) is one of the most dreaded Angular errors. It happens when a binding value changes after Angular's change detection has already checked that binding in the current cycle.

Symptoms

  • Console error in development mode only (production silently fails)
  • "Expression has changed after it was checked" message with old and new values
  • Component renders but with wrong initial state
  • Error appears during component initialization, not user interaction

Real Error Message

bash
NG0100: ExpressionChangedAfterItHasBeenCheckedError:
Expression has changed after it was checked. Previous value:
'disabled: false'. Current value: 'disabled: true'.
    at viewDebugError (core.js:8645)
    at checkBindingNoChanges (core.js:8785)

The Root Cause

Angular runs change detection twice in development mode to verify stability. If a value changes between the first and second check, Angular throws NG0100. This typically happens when:

  1. 1.A child component modifies a parent-bound property during ngOnInit
  2. 2.An async pipe resolves during the same tick
  3. 3.A directive changes a bound property during initialization

Real Error Scenario

```typescript // Parent template <app-child [isValid]="isValid"></app-child>

// Child component @Component({...}) export class ChildComponent implements OnInit { @Input() isValid: boolean;

ngOnInit() { this.isValid = this.validate(); // CHANGES PARENT'S BINDING! }

validate() { return this.checkSomething(); } } ```

The parent's isValid is checked during the parent's change detection. Then the child's ngOnInit changes it. Angular's second check detects the mismatch.

How to Fix It

Fix 1: Use ngAfterViewInit for Child-Initiated Changes

```typescript @Component({...}) export class ChildComponent implements AfterViewInit { @Input() isValid: boolean; private _internalValid = false;

ngAfterViewInit() { this._internalValid = this.validate(); this.validationChange.emit(this._internalValid); }

@Output() validationChange = new EventEmitter<boolean>(); } ```

Emit an event instead of mutating the input directly.

Fix 2: Use ChangeDetectorRef

```typescript import { ChangeDetectorRef } from '@angular/core';

@Component({...}) export class ChildComponent implements OnInit { @Input() isValid: boolean;

constructor(private cdr: ChangeDetectorRef) {}

ngOnInit() { this.isValid = this.validate(); this.cdr.detectChanges(); // Run change detection immediately } } ```

Fix 3: Use setTimeout to Defer the Change

typescript
ngOnInit() {
  setTimeout(() => {
    this.isValid = this.validate(); // Runs after current change detection cycle
  });
}

Fix 4: Compute the Value in the Parent

```typescript // Parent component export class ParentComponent { isValid = this.computeValidation();

computeValidation() { // Compute it here where the binding originates return this.checkSomething(); } } ```

If the value is derived, compute it at the source rather than having the child modify it.

Fix 5: Run Outside Angular Zone

```typescript import { NgZone } from '@angular/core';

constructor(private zone: NgZone) {}

ngOnInit() { this.zone.runOutsideAngular(() => { setTimeout(() => { this.zone.run(() => { this.isValid = this.validate(); }); }); }); } ```