Angular Runtime Errors Explained: NG0100 to NG0956 Catalog [2026]

Link copied
Angular Runtime Errors Explained: NG0100 to NG0956 Catalog [2026]

Angular Runtime Errors Explained: NG0100 to NG0956 Catalog [2026]

Sooner or later every Angular developer stares at a red console line that starts with NG0 and a number. The message is often short, sometimes cryptic, and in a production build it may be little more than the code and a link. The good news is that the same couple of dozen codes cover most of what you'll hit in real projects, and each one points at a specific mistake with a specific fix.

This is lesson 10.1 of the Angular Tutorial, and the first lesson of Module 10, which is about debugging the problems real apps run into. It follows lesson 9.7 on SSR caching. This lesson is a reference page: for each common runtime error you'll get what it means, a minimal reproduction and the fix. Bookmark it, and use the rest of the module for the deeper investigations.

How to read an Angular error #

Every Angular error has a code. The prefix and number tell you a lot before you read the message:

Code shape Raised by When you see it
NG0xxx (for example NG0201) The runtime, in the browser or on the server While the app runs
NG0xxxx (for example NG01203, NG02802) A specific package (forms, common/http, platform) While the app runs
NG1xxx–NG8xxx (for example NG8001) The compiler During ng build or ng serve, before anything runs

Three habits make any error easier:

  • Open the link. Each code has a page at angular.dev/errors/<CODE> with the cause and fix. Production builds strip most of the message text to keep bundles small, so the code and link may be all you get. Reproduce in a development build to see the full message.
  • Read the whole stack. The first frame is usually inside Angular. Scroll to the first frame in your code; with source maps on, it points at the component or template expression involved.
  • Check whether it's an error or a warning. Some codes (the @for tracking ones, for example) are logged as warnings in development and don't stop the app. They still point at real bugs.

Change detection and signals #

NG0100: Expression changed after it was checked #

In development mode, Angular runs a second check after each change detection pass to confirm that no binding changed while the view was being updated. If one did, the view is out of sync with your data and Angular throws NG0100. It only throws in development mode.

@Component({
  selector: 'app-title',
  template: `<h1>{{ title }}</h1>`,
})
export class Title implements AfterViewInit {
  title = 'Loading';
  ngAfterViewInit() {
    this.title = 'Dashboard';   // the view was already checked: NG0100
  }
}

Fix: set initial values in the constructor, a field initializer or ngOnInit; don't let template methods change other bindings; derive values with computed() instead of assigning them late. Lesson 10.2 covers every cause in depth.

NG0600: Signal write in a disallowed context #

You wrote to a signal (set or update) inside a computed(), a linkedSignal() computation, or during template rendering. Those must be pure reads.

doubled = computed(() => {
  this.total.set(this.count() * 2);   // NG0600: writing inside computed
  return this.count() * 2;
});

Fix: make the dependent value a computed() itself, and move genuine writes to event handlers, lifecycle hooks or effects. untracked() is a last resort.

NG0602: Disallowed function call inside a reactive context #

Calling effect(), afterNextRender(), afterEveryRender() or toSignal() inside a computed() (or a running effect) creates a new effect or subscription every time it re-evaluates.

// ✗ NG0602
result = computed(() => doSomething(toSignal(this.data$)()));

// ✓ create it once, read it inside
data = toSignal(this.data$);
result = computed(() => doSomething(this.data()));

Fix: create effects, render hooks and toSignal() once, in a field initializer or the constructor.

NG0950: Required input is accessed before a value is set #

A required input (input.required()) has no value until Angular binds it. Reading it in the constructor or a field initializer throws.

export class UserCard {
  user = input.required<User>();
  name = this.user().name;    // NG0950: read during construction
}

Fix: read required inputs in the template, inside computed() or an effect, or in ngOnInit and later: name = computed(() => this.user().name);.

NG0951: Required query has no value #

viewChild.required() or contentChild.required() was read before the query resolved, or it matched nothing.

export class Chart {
  canvas = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');
  constructor() {
    this.canvas();            // NG0951: the view doesn't exist yet
  }
}

Fix: read view queries in afterNextRender() or ngAfterViewInit and later (content queries in ngAfterContentInit and later), and make sure the template really contains a match. If the element is inside an @if, the query isn't required; use viewChild() and handle undefined.

NG0955 and NG0956: @for tracking problems #

Both are development-mode warnings about the track expression in @for:

  • NG0955 means the track expression produced duplicate keys, for example track item.name when two items share a name. Angular may reuse the wrong DOM nodes and falls back to slower bookkeeping.
  • NG0956 means a change re-created the DOM for every item, usually because you track by object identity (track item) and the data was replaced with new objects, for example after a re-fetch or an immutable update.
<!-- ✗ NG0956 after every re-fetch; ✗ NG0955 if names repeat -->
@for (todo of todos(); track todo) { <li>{{ todo.task }}</li> }

<!-- ✓ -->
@for (todo of todos(); track todo.id) { <li>{{ todo.task }}</li> }

Fix: track by a property that is unique and stable for each item. Lesson 8.3 explains tracking in detail.

Dependency injection #

NG0201: No provider found #

You injected something that no injector in the hierarchy provides.

export class ReportService {}           // no @Injectable, no providers entry

@Component({ /* ... */ })
export class Report {
  private reports = inject(ReportService);   // NG0201
}

Fix: mark the service @Injectable({ providedIn: 'root' }), or add it to a providers array that is an ancestor of the place you inject it (the component, a route, or app.config.ts). The message names the missing token; work backwards from the class that asked for it. A common variant is a providers entry on a sibling component, which the injecting component can't see.

NG0200: Circular dependency in DI #

Service A injects B, and B (directly or through others) injects A. Angular can't construct either.

@Injectable({ providedIn: 'root' })
export class UserService { private emp = inject(EmployeeService); }

@Injectable({ providedIn: 'root' })
export class EmployeeService { private users = inject(UserService); }  // NG0200

Fix: break the loop. Move the shared logic into a third service both can inject, or pass data as method arguments instead of injecting the other service. The call stack lists the chain.

NG0203: inject() must be called from an injection context #

inject() only works while Angular is creating something: in a field initializer, a constructor, a provider factory, or inside runInInjectionContext(). Route guards, resolvers and functional interceptors also run in an injection context.

export class Car {
  ngOnInit() {
    const engine = inject(Engine);   // NG0203: too late
  }
}

Fix: move the call to a field initializer (engine = inject(Engine);). The same error appears when you call takeUntilDestroyed(), toSignal() or effect() from a method without passing a DestroyRef or Injector; pass one explicitly or create them at construction time. In tests, use TestBed.runInInjectionContext().

Templates and compilation #

NG0300: Multiple components match the same element #

Two components have selectors that both match one element.

<!-- StrokedButton: selector '[stroked-button]', RaisedButton: '[raised-button]' -->
<button stroked-button raised-button>Save</button>   <!-- NG0300 -->

Fix: an element can host only one component. Apply only one, turn one of them into a directive, or rename a selector that clashes with a library's.

NG0302: Pipe not found #

A template uses a pipe that isn't imported into the component, or the name doesn't match the pipe's name.

@Component({
  template: `{{ price | currencyShort }}`,   // NG0302: pipe not imported
})
export class Price {}

Fix: add the pipe class to the component's imports array and check the name in its @Pipe({ name }).

NG8001 and NG8002: Unknown element or property #

These are compiler errors, so you normally see them in the terminal during ng serve or ng build:

  • NG8001: an element isn't a known HTML element and matches no imported component (for example, you used <app-avatar> but didn't import Avatar). The runtime equivalent is NG0304.
  • NG8002: you bound to a property that isn't a native property of the element and isn't an input of any directive on it, usually a typo or a missing import.

Fix: import the component or directive into the standalone component's imports. For genuine Web Components, add schemas: [CUSTOM_ELEMENTS_SCHEMA] to that component.

NG0919: Circular dependency between components #

Component A imports component B in its imports, and B imports A, directly or through other files.

Fix: move shared pieces into a third component, restructure the hierarchy so the dependency runs one way, or use import type where you only need the type.

Security #

NG0910: Unsafe bindings on an iframe element #

You bound a security-sensitive <iframe> attribute (sandbox, allow, allowFullscreen, referrerPolicy, csp, fetchPriority or credentialless). These must be set before src, so Angular requires them to be static.

<iframe [sandbox]="mode()" [src]="url()"></iframe>   <!-- NG0910 -->

Fix: use static attributes, and switch between whole <iframe> elements with @if when the value really varies. Lesson 8.9 covers the rest of Angular's security model.

Data, forms and HTTP #

NG0991: Resource completed before producing a value #

An Observable-based resource (rxResource() or httpResource()) completed without emitting a value or an error. The classic cause is catchError(() => EMPTY).

stream: ({ params }) =>
  this.http.get<User>(`/api/users/${params.id}`).pipe(
    catchError(() => EMPTY),   // NG0991: completes with nothing
  ),

Fix: let the error propagate (the resource exposes it through error()), or return a fallback with catchError(() => of(null)).

NG01203: Missing value accessor #

A form directive (formControl, formControlName, ngModel) is on an element that has no ControlValueAccessor: a custom component that doesn't register one, or a plain <div>.

Fix: implement ControlValueAccessor in the custom control and provide it with NG_VALUE_ACCESSOR (useExisting: forwardRef(() => MyInput), multi: true). For third-party controls that already handle the value, ngDefaultControl may be enough.

NG02802: Headers not transferred by HttpTransferCache #

With SSR, the HTTP transfer cache replays server responses on the client but drops response headers by default. Your client code then read a header that wasn't transferred.

Fix: list the headers you need, per request (transferCache: { includeHeaders: ['etag'] }) or app-wide with withHttpTransferCacheOptions({ includeHeaders: [...] }).

Hydration (NG05xx) #

When server-rendered HTML and the client's view disagree, hydration fails with a code in the 500 range:

Code Meaning Typical cause
NG0500 Node mismatch Client found a different element than the server rendered
NG0501 Missing siblings @if/@for rendered a different number of nodes on the client
NG0502 Missing node An expected node isn't there at all
NG0503 Unsupported projection of DOM nodes Natively created nodes passed as projectableNodes
NG0504 ngSkipHydration on an invalid node Attribute used on something other than a component host
NG0505 No hydration info in server response provideClientHydration() missing from the server config
NG0506 App remains unstable Pending timers, requests or an effect loop keep the app from becoming stable
NG0507 HTML altered after SSR A CDN or post-processing step stripped whitespace or comment nodes

These usually come from invalid HTML nesting, direct DOM manipulation, or values that differ between server and browser. Lesson 10.8 is a full debugging walkthrough; lesson 9.3 covers the patterns that avoid them.

Quick lookup #

You see First thing to check
NG0100 A value changed in ngAfterViewInit, a template method, or a child writing to a parent
NG0200 / NG0919 Two services (DI) or two components (imports) depend on each other
NG0201 providedIn: 'root' or a providers entry on an ancestor
NG0203 inject() (or toSignal, takeUntilDestroyed, effect) called outside construction
NG0302 / NG8001 / NG8002 The component's imports array
NG0600 / NG0602 Side effects inside computed() or a template method
NG0950 / NG0951 A required input or query read in the constructor
NG0955 / NG0956 The track expression
NG0991 catchError(() => EMPTY) in a resource stream
NG05xx Invalid HTML, DOM manipulation, or server/client differences

Gotchas #

Symptom Cause Fix
Production error shows only a code and a link Messages are stripped from production bundles Reproduce in a development build to read the full message
NG0100 appears locally but never in production The check only runs in development mode Fix it anyway; production users see the stale value
NG0201 only in tests TestBed doesn't include the providers from app.config.ts Add the provider (or a fake) in TestBed.configureTestingModule
NG0203 after moving code into a helper function The helper is called later, outside construction Pass an Injector or DestroyRef, or call the helper from a field initializer
NG8001 for a component you're sure exists It isn't in this component's imports Import it where it's used, not only in the parent
NG0956 after switching to immutable updates track item relies on object identity track item.id

What's next #

Lesson 10.2 takes the most famous error on this page, NG0100 (ExpressionChangedAfterItHasBeenChecked), and works through why the check exists, its five common root causes, and how signals and zoneless change detection change the picture. The rest of Module 10 covers components that don't update, duplicate HTTP calls, leaks, race conditions, guards that don't fire, and hydration mismatches.

Try it yourself #

Open your app's development build, clear the console, and click through its main flows with "Preserve log" turned on. Write down every NG code you see, warnings included, look each one up on this page, and fix the one that appears most often.

YouI moved our analytics setup into a helper called trackPageViews() and now I get NG0203 when the dashboard loads. The same code worked when it was in the constructor. What changed?
Claude · used search_documentationNG0203 means inject() ran outside an injection context. trackPageViews() calls inject(Router) and takeUntilDestroyed(), and you now call it from ngOnInit, after the component has been constructed. Two fixes: (1) call it from a field initializer (private pageViews = trackPageViews();) so it runs during construction, or (2) keep calling it later but give it an injector: inject Injector in the component and wrap the call in runInInjectionContext(this.injector, () => trackPageViews()). Option 1 is simpler and also means takeUntilDestroyed() picks up the component’s DestroyRef automatically.

Up next in Angular

More from this topic

View all Angular articles →
Angular

When Angular is launched ?

Link copied Angular When Angular is launched ? February 8, 2024 · 1 min read When was Angular launched? # Angu…

Feb 8, 2024 Read →

Enjoyed this article?

Get new Angular tutorials delivered. No spam — just code-first articles when they ship.

1 comment

1 thought on “Angular Runtime Errors Explained: NG0100 to NG0956 Catalog [2026]”

  1. Pingback: Fix NG0100 ExpressionChangedAfterItHasBeenChecked [2026]

Leave a Comment

Your email stays private. Required fields are marked *

1 thought on “Angular Runtime Errors Explained: NG0100 to NG0956 Catalog [2026]”

  1. Pingback: Fix NG0100 ExpressionChangedAfterItHasBeenChecked [2026]

Leave a Comment

Your email stays private. Required fields are marked *