Runtime Errors
Esta página aún no está disponible en tu idioma.
NG0100 — ExpressionChangedAfterItHasBeenCheckedError
Section titled “NG0100 — ExpressionChangedAfterItHasBeenCheckedError”Symptom
NG0100: ExpressionChangedAfterItHasBeenCheckedError:Expression has changed after it was checked.Previous value: 'false'. Current value: 'true'.Only appears in development mode.
Cause Angular runs a second verification pass in dev mode. If a binding produces a different value in that second pass, the template and the model are out of sync. Typical triggers:
- A child component mutates a parent property in
ngOnInit/ngAfterViewInit. - A getter in the template returns a new object or a computed value with side effects.
- A shared service is updated during the rendering phase.
Diagnosis The error message shows the previous and current values. Search the template for that binding. If the value is produced by a getter, that getter is almost always the culprit.
Fix 1 — Move the mutation out of the render phase
// ❌ Mutates the parent while the parent is being checkedngAfterViewInit(): void { this.parent.isLoading = false;}
// ✅ Defer to the next microtask / renderconstructor() { afterNextRender(() => this.parent.isLoading = false);}Fix 2 — Replace getters with signals
// ❌ New array on every check → always "changed"get visibleItems(): Item[] { return this.items.filter(i => i.visible);}
// ✅ Memoised, stable referencereadonly visibleItems = computed(() => this.items().filter(i => i.visible));Fix 3 — Last resort
private readonly cdr = inject(ChangeDetectorRef);this.cdr.detectChanges();Prevention
- Never call methods or getters that allocate in templates — use
computed(). - Treat inputs as read-only; communicate upwards with outputs.
NG0955 — Duplicate keys in @for track
Section titled “NG0955 — Duplicate keys in @for track”Symptom
NG0955: The provided track expression resulted in duplicated keysfor a given collection.Cause
track must return a unique and stable value per item. Duplicates make Angular unable to map DOM nodes to items.
Diagnosis
const keys = items.map(i => i.id);console.assert(new Set(keys).size === keys.length, 'duplicate track keys');Fix
<!-- ❌ $index is not stable when the list is sorted or filtered -->@for (item of items(); track $index) { ... }
<!-- ❌ Not unique if the same product appears twice in a cart -->@for (line of cart(); track line.productId) { ... }
<!-- ✅ Unique per row -->@for (line of cart(); track line.lineId) { ... }If the data has no natural id, compose one:
@for (row of rows(); track row.userId + '-' + row.date) { ... }Prevention
Make ids part of the DTO contract. Only fall back to $index for static lists that never reorder.
NG0956 — Tracking expression caused re-creation of the entire collection
Section titled “NG0956 — Tracking expression caused re-creation of the entire collection”Symptom The whole list flashes / loses scroll position and focus on every refresh, plus a console warning.
Cause
track returns a value that changes on every emission — typically the object reference itself when the API response is re-mapped.
Fix
<!-- ❌ New object references on every HTTP response -->@for (user of users(); track user) { ... }
<!-- ✅ Stable primitive -->@for (user of users(); track user.id) { ... }Prevention Never track by object reference unless the objects are genuinely immutable and reused.
NG0301 — Export not found
Section titled “NG0301 — Export not found”Symptom
NG0301: Export of name 'ngModel' not found!Cause
You used a template reference to a directive export (#ref="ngModel", #f="ngForm", #tooltip="matTooltip") without importing the directive in the standalone component.
Fix
@Component({ imports: [FormsModule], // provides ngModel / ngForm})Prevention
In standalone components, every directive used in the template must appear in imports. Rely on the Angular Language Service quick-fix.
NG0750 — @defer requires standalone dependencies
Section titled “NG0750 — @defer requires standalone dependencies”Symptom
NG0750: @defer block requires all dependencies to be standalone.Cause
A component used inside a @defer block is declared in an NgModule instead of being standalone. Deferred blocks can only lazy-load standalone artefacts.
Fix Convert the component to standalone:
ng generate @angular/core:standalonePrevention
New code should always be standalone. Track remaining NgModule declarations as technical debt.
NG02200 — Cannot find a differ
Section titled “NG02200 — Cannot find a differ”Symptom
NG02200: Cannot find a differ supporting object '[object Object]' of type 'object'.NgFor only supports binding to Iterables such as Arrays.Cause
You bound an object, null, undefined or a Map where an array was expected.
Fix
<!-- ✅ Guard against null while loading -->@for (item of items() ?? []; track item.id) { ... }
<!-- ✅ Iterate an object explicitly -->@for (entry of dictionary() | keyvalue; track entry.key) { ... }Prevention
Initialise collection signals with signal<Item[]>([]), never with undefined.
NG04002 — Cannot match any routes
Section titled “NG04002 — Cannot match any routes”Symptom
NG04002: Cannot match any routes. URL Segment: 'products/42'Cause (in order of likelihood)
- The route is declared in a lazy
Routesarray that is never loaded. - Route order — a
**or a broader path matches first. - A missing leading/trailing segment after a redirect.
- A
canMatchguard rejected every candidate silently.
Diagnosis Enable router tracing:
provideRouter(routes, withDebugTracing())Fix
- Put specific routes before generic ones;
{ path: '**' }must always be last. - Check
redirectTousespathMatch: 'full'when redirecting from''.
Prevention
Add a routing smoke test that navigates to every top-level route and asserts no NavigationError.
Infinite change detection loop
Section titled “Infinite change detection loop”Symptom
The tab freezes; the Profiler shows endless change detection cycles; in zoneless mode you may see ASSERTION ERROR: Infinite loop.
Cause
An effect() writes to a signal that it also reads, directly or through a chain.
// ❌ Reads and writes the same signaleffect(() => { this.count.set(this.count() + 1);});Fix
Use computed() for derived state. Reserve effect() for genuine side effects (logging, DOM, storage) and never write signals that the effect reads.
readonly doubled = computed(() => this.count() * 2);If you truly need to write, isolate the read:
effect(() => { const value = this.source(); untracked(() => this.target.set(transform(value)));});Prevention
Treat effect() as a code smell in business logic. See Signals and reactivity.