Diagnosing Angular's ExpressionChangedAfterItHasBeenCheckedError (NG0100)
Diagnostic guide for Angular's NG0100 ExpressionChangedAfterItHasBeenCheckedError: recognize the dev‑only symptom, trace mid‑cycle state writes, apply the fix matched to the cause, and know when to escalate to a data‑flow redesign.
13 Jun 2026, 04:39 UTC

ExpressionChangedAfterItHasBeenCheckedError (NG0100). The error message names the component, the binding, and the previous/current values. Production builds skip the verification pass, so the bug is hidden there but the inconsistent render still occurs.
Common causes at a glance
| Observed pattern in error | Typical source |
|---|---|
| Previous value is undefined/null, current value is real data | Child writes to a parent‑bound property in ngAfterViewInit or ngAfterContentInit |
| Value flips between two real values | Async completion (HTTP, setTimeout) or event handler mutating state already rendered |
| Numeric value (width, scrollTop) differs slightly | Template binds to a field updated from native element measurements |
| Error follows the child component’s name | Child emits/sets state upward during its own init hooks |
Ordered diagnostic checks
- Read the error literally. Note the component and property named. Locate that binding in the template.
- Search for every write to that property: component class, children, injected services. Focus on ngAfterViewInit, ngAfterContentInit, subscriptions, timers.
- Check the direction of data flow. If a child writes to state its parent already rendered this cycle, that is the culprit.
- Rule out measurement reads. If the property holds element dimensions or scroll position read via ElementRef/ViewChild, the DOM settled after the check ran.
- Temporarily enable OnPush on the affected component. If the error disappears or moves, the mutation is coming from outside the declared inputs.
Fixes matched to findings
Lifecycle‑order cause: move the write earlier
If a child initializes state in ngAfterViewInit that an ancestor template already rendered, move the initialization to ngOnInit (which runs before the view is checked) or restructure so the parent owns the state and passes it down:
// Before: throws NG0100 — parent already rendered 'items' as undefined ngAfterViewInit() { this.items = this.buildItems(); }
// After: value exists before the first check ngOnInit() { this.items = this.buildItems(); }
Async or unavoidable late write: defer past the current cycle
When the write genuinely cannot happen earlier (a measurement, a late‑arriving event), push it past the current check. Inject ChangeDetectorRef and re‑run detection synchronously:
constructor(private cdr: ChangeDetectorRef) {} ngAfterViewInit() { this.width = this.el.nativeElement.offsetWidth; this.cdr.detectChanges(); // re‑checks this component's bindings now }
Alternatives are queueMicrotask(...) or Promise.resolve().then(...), which schedule the write after the current cycle. detectChanges() is generally preferable: it is synchronous, explicit, and easy to trigger in a TestBed unit test. Caution: calling detectChanges() on a parent inside its ngAfterViewInit can itself trigger NG0100 in children — verify the data‑flow direction first. For element‑size tracking specifically, a ResizeObserver writing to a signal or an OnPush‑marked field is cleaner than polling in a hook.
Recurring pattern: narrow the surface with signals and OnPush
Signals (stable since Angular 17, evolving through v20) make dependencies explicit: a template binding reads a signal, and only that binding updates when the signal changes. Converting the offending property to a signal often eliminates the class of error because writes are tracked rather than discovered by a verification pass:
readonly items = signal<Item[]>([]); load() { this.http.get('/api/items') .subscribe(data => this.items.set(data)); }
Zoneless change detection (developer preview in recent versions) removes Zone.js timing from the equation entirely. Both APIs changed across Angular 16‑20 — confirm semantics against your installed version before adopting.
When to escalate
A single NG0100 is a local bug. If you are sprinkling detectChanges() across many components, or the error keeps reappearing in new forms, that is an architecture smell: state is flowing upward or sideways during render. Escalate to a state‑flow review: enforce unidirectional data flow (parents own state, children receive inputs and emit events), adopt OnPush as the default, and consider signals or a store for shared state. Patching each site individually just relocates the inconsistency.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.