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
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.A child component modifies a parent-bound property during
ngOnInit - 2.An
asyncpipe resolves during the same tick - 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
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(); }); }); }); } ```