---
title: "NG0203: Why inject() Fails Outside an Injection Context"
description: "Why Angular throws NG0203 when inject() runs outside an injection context, the exact window where inject() is valid, the common traps (subscribe, async route guard, class method, getter), and the fixes ranked from field initializer to runInInjectionContext."
deck: "`NG0203` means you called [`inject()`](https://angular.dev/api/core/inject) after Angular finished building the class. The injection context is open only during construction: in a field initializer, in the constructor body, or inside [`runInInjectionContext`](https://angular.dev/api/core/runInInjectionContext). Call it later, from a `subscribe`, a `setTimeout`, a getter, or an event handler, and it throws. Source: [angular.dev/errors/NG0203](https://angular.dev/errors/NG0203)."
author: "André Ramos"
url: "https://andreramos.dev/angular/angular-ng0203-inject-outside-injection-context/"
lang: "en"
type: "article"
---

# NG0203: Why inject() Fails Outside an Injection Context

> `NG0203` means you called [`inject()`](https://angular.dev/api/core/inject) after Angular finished building the class. The injection context is open only during construction: in a field initializer, in the constructor body, or inside [`runInInjectionContext`](https://angular.dev/api/core/runInInjectionContext). Call it later, from a `subscribe`, a `setTimeout`, a getter, or an event handler, and it throws. Source: [angular.dev/errors/NG0203](https://angular.dev/errors/NG0203).

## The window, and when it closes

The full message is `inject() must be called from an injection context`. The injection context is the runtime window where the current injector is reachable, and the docs are exact about when it exists: during construction of a class the DI system instantiates, in the field initializers of that class, in a `useFactory` or `InjectionToken` factory, and inside any stackframe run through `runInInjectionContext`. That is the whole list.

The trap is that the window is narrow. It is open while Angular is building the instance and closed the moment construction finishes. The API reference says it plainly: calls to `inject()` are disallowed after a class instance was already created, in methods including lifecycle hooks. So the question is never *can I inject this type*; the provider exists. The question is *was the injection context still open when this line ran*.

## Where inject() works, and where it throws

Most NG0203 reports come from one of a short list of call sites. The pattern is consistent: code that runs synchronously while Angular builds the class is fine, and code deferred to a callback, a timer, a stream, or a method body has already missed the window.

Read the table by call site, not by type. The type is injectable in every row. What changes is whether the line executes inside the construction window or after it.

| Call site | inject() here? | Why |
|---|---|---|
| Field initializer (`private x = inject(X)`) | Works | Runs during construction, the context is open. |
| Constructor body | Works | Still inside construction. |
| `useFactory` / `InjectionToken` factory | Works | Factories run inside the DI context. |
| Inside `runInInjectionContext(injector, fn)` | Works | The injector is made available for that stackframe. |
| Lifecycle hook body (`ngOnInit`) | Throws NG0203 | Runs after the instance was created. |
| `subscribe` / `.then` / `setTimeout` callback | Throws NG0203 | Deferred work, the context closed long ago. |
| Class method or getter | Throws NG0203 | Called after construction, outside the window. |
| Event handler (`(click)` target) | Throws NG0203 | Fires on user action, well past construction. |

## The traps that look harmless

The first trap is `inject()` inside a `subscribe`. The constructor looks fine, the call site looks fine, but the callback runs when the value arrives, not when the class is built, and by then the context is closed.

The second is the async route guard. A `CanActivateFn` runs in an injection context when it starts, so the first `inject()` is fine. Put an `await` before the next one and you have crossed out of the synchronous window: the docs state inject is only usable synchronously and cannot be used after any await points. The third is the quiet one: a private getter or a helper method that calls `inject()`. It reads like field code, but it executes on access, long after construction.

*Three call sites that throw NG0203*

```ts
export class OrdersComponent {
  private readonly orders = inject(OrderService);

  ngOnInit() {
    this.orders.changes$.subscribe(() => {
      // NG0203: the callback runs after construction
      const logger = inject(LoggerService);
      logger.track('orders changed');
    });
  }

  // NG0203: a getter runs on access, not during construction
  get currency() {
    return inject(LOCALE_ID) === 'pt-BR' ? 'BRL' : 'USD';
  }
}
```

## The fixes, ranked

Start at the top of this list and only move down when the case forces you to. Most NG0203 disappears at step one, because the injected reference did not need to be resolved late; the code was just written late.

First, move the `inject()` to a field initializer or capture the reference once at construction and use it later. The field holds the dependency; the callback uses the field. Second, when you genuinely need resolution inside deferred code (a dynamically chosen token, a per-call injector), capture the `Injector` at construction and call `runInInjectionContext(injector, () => inject(X))` where the work runs. Third, for the specific RxJS-cleanup case, `takeUntilDestroyed` accepts an explicit `DestroyRef`, so you can capture `inject(DestroyRef)` once and pass it into the operator outside the context.

I would refuse the reflex fix of wrapping every late call in `runInInjectionContext`. It silences NG0203, but it usually means the design deferred a decision that belonged at construction. When I see it in review, I ask what is actually dynamic. If the answer is *nothing*, the reference should be a field, not a runtime lookup.

*Capture at construction, use in the callback*

```ts
export class OrdersComponent {
  private readonly orders = inject(OrderService);
  // Capture once, in the construction window:
  private readonly logger = inject(LoggerService);
  private readonly destroyRef = inject(DestroyRef);

  ngOnInit() {
    this.orders.changes$
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => this.logger.track('orders changed'));
  }
}
```

## runInInjectionContext, and the await that breaks it

When the lookup truly is deferred, inject the `Injector` (or `EnvironmentInjector`) at construction and reopen the context where you need it. The signature is `runInInjectionContext(injector, fn)`: it runs `fn` with that injector available, so `inject()` works inside the closure and returns its result.

There is one edge that fools people, and it is worth saying out loud: the context `runInInjectionContext` opens is synchronous. The moment you `await` inside the closure, you fall out of it, and an `inject()` after the await throws NG0203 again. So resolve everything you need before the first await, then do the async work. This is the same rule that bites async route guards, and it is the single most common reason a `runInInjectionContext` fix still fails.

*Inject the Injector, reopen the context later*

```ts
export class ReportExporter {
  private readonly injector = inject(Injector);

  exportLater() {
    setTimeout(() => {
      runInInjectionContext(this.injector, () => {
        // Resolve synchronously, before any await:
        const http = inject(HttpClient);
        const url = inject(REPORT_URL);
        http.get(url).subscribe();
      });
    }, 1000);
  }
}
```

## Reusable artifact: Resolving NG0203 inject() outside an injection context

- Confirm the call site: a field initializer, constructor, or factory is inside the window; a callback, method, getter, or event handler is outside it.
- If the reference is not actually dynamic, move inject() to a field initializer and use the field in the callback.
- Capture inject(DestroyRef) at construction and pass it into takeUntilDestroyed to unsubscribe outside the context.
- When resolution must be deferred, inject(Injector) at construction and wrap the late code in runInInjectionContext(injector, fn).
- Inside runInInjectionContext, resolve every inject() before the first await; the context is synchronous and an await drops you out of it.
- Treat a reflex runInInjectionContext as a smell: if nothing is dynamic, the dependency belongs in a field, not a runtime lookup.

## FAQ

### What does NG0203 mean in Angular?

NG0203 means inject() ran outside an injection context. The full message is 'inject() must be called from an injection context'. The injection context is open only while Angular builds the class (in a field initializer, the constructor, a factory, or inside runInInjectionContext) and closes once construction finishes.

### Where can inject() be called without throwing NG0203?

In a field initializer, in the constructor body, in a useFactory or InjectionToken factory, and inside runInInjectionContext. It throws in lifecycle hook bodies, subscribe and setTimeout callbacks, class methods, getters, and event handlers, because those run after construction.

### How do I use inject() inside a subscribe or setTimeout?

Capture the dependency at construction (a field initializer) and use the field in the callback. If resolution must happen late, inject the Injector at construction and call runInInjectionContext(injector, () => inject(X)) where the deferred code runs.

### Why does inject() still throw NG0203 after an await?

The injection context, including the one runInInjectionContext opens, is synchronous. After an await you have left it, so a later inject() throws. Resolve everything you need before the first await, then do the async work.

## Sources checked

- https://angular.dev/errors/NG0203
- https://angular.dev/api/core/inject
- https://angular.dev/api/core/runInInjectionContext
- https://angular.dev/guide/di/dependency-injection-context
- https://angular.dev/guide/di
- https://angular.dev/api/core/rxjs-interop/takeUntilDestroyed
- https://angular.dev/api/core/DestroyRef

## Related

- [NG0200 Circular Dependency in DI: Reading the Chain and Breaking It](https://andreramos.dev/angular/angular-ng0200-circular-dependency-di/)
- [NG0950: Reading a Required Input Before Angular Sets It](https://andreramos.dev/angular/angular-ng0950-required-input-not-set/)
- [Facade Pattern in Angular: When Components Know Too Much](https://andreramos.dev/angular/facade-pattern-in-angular/)
