skip to content

Inject Function & Context

inject() resolves a dependency only inside an injection context: field initializers, constructors, factories and guards. Interviewers ask about NG0203 and runInInjectionContext.

part ofAngularoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In Angular, what is the difference between injecting with inject() and injecting through constructor parameters, and which does the style guide recommend?

level: juniorimportance: must knowfreq 70%

answer

  1. same injector, same resolution
  2. explicit token vs parameter type
  3. inheritance without super arguments
  4. field order matters
  5. usable in plain functions

basics

~20 s

Both resolve dependencies from the same injector in the same way. inject() is called in a field initializer or constructor, giving better type inference, no super() forwarding and use in plain functions; Angular's style guide prefers it.

solid answer

~40 s

Constructor injection declares dependencies as parameters, `constructor(private cart: Cart) {}`, and the compiler turns the parameter types into a factory. `inject(Cart)` asks for the same thing explicitly, usually in a field initializer: `private cart = inject(Cart)`. Resolution is identical — same injector, same lookup — so the difference is ergonomics. `inject()` takes the token as an argument, so `InjectionToken`s need no `@Inject` decorator and the type is inferred; subclasses do not have to repeat and forward the base class's dependencies through `super(...)`; and functions such as guards and provider factories can call it, which constructors cannot help with. Angular's style guide recommends `inject()`. The catch is that it only works in an injection context, and field initializers run top to bottom, so a field that uses an injected dependency must be declared after it.

code

ts · 23 lines
ts
import { Component, Injectable, InjectionToken, inject, signal } from '@angular/core';

export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL', { factory: () => '/api' });

@Injectable({ providedIn: 'root' })
export class Cart {
  readonly items = signal<string[]>([]);
}

// Base class resolves its own dependencies.
export abstract class PageBase {
  protected readonly baseUrl = inject(API_BASE_URL); // typed as string, no @Inject
}

@Component({
  selector: 'app-checkout-page',
  template: `<p>{{ itemCount }} items, API at {{ baseUrl }}</p>`,
})
export class CheckoutPage extends PageBase {
  private readonly cart = inject(Cart); // injected fields first
  protected readonly itemCount = this.cart.items().length; // safe: cart already assigned
  // No constructor needed: nothing to forward to super().
}

go deeper

for a junior

Know both syntaxes, that they resolve the same way, and that the style guide prefers inject() in field initializers.

for a middle

Explain the practical advantages — token typing, no @Inject, inheritance without forwarding — and the declaration-order trap.

for a senior

Use inject() to simplify base classes and shared helpers while keeping calls inside construction time, and review for fields that read dependencies too early.

for a principal

Set one injection style for the codebase, schedule the mechanical migration, and make field-order and context rules part of review guidance.

## Two ways to ask for a dependency Angular's **dependency injection** (DI) creates components, directives and services and supplies what they need. A class can state its needs in two ways. **Constructor parameters** — the classic form: ```ts export class CheckoutPage { constructor(private readonly cart: Cart, @Inject(API_BASE_URL) private readonly baseUrl: string) {} } ``` The Angular compiler reads the parameter types and generates a factory that resolves each one. A parameter whose type is not a usable token — a string resolved through an `InjectionToken`, for instance — needs the `@Inject(TOKEN)` parameter decorator. **The `inject()` function** — the current recommendation: ```ts export class CheckoutPage { private readonly cart = inject(Cart); private readonly baseUrl = inject(API_BASE_URL); // typed as string } ``` Both forms are resolved by the same injector with the same rules. The style guide says `inject()` "works the same way as constructor parameter injection"; the reasons to prefer it are about code, not behaviour. ## Why the style guide prefers `inject()` | Concern | Constructor parameters | `inject()` | |---|---|---| | Token that is not a class | needs `@Inject(TOKEN)` | pass the token directly | | Type of the result | written by hand | inferred from the token | | Base class with dependencies | subclass must accept and forward them via `super(...)` | base class resolves its own; subclass calls `super()` with no DI arguments | | Comments per dependency | awkward inside a parameter list | natural on each field | | Fields computed from a dependency | may need separate declaration and assignment | can be initialized inline, after the injected field | | Plain functions (guards, factories) | not applicable | works in their injection context | The inheritance row is often the deciding one in real codebases: adding a dependency to a base class with constructor injection forces edits to every subclass constructor. ## Same moment, same injector It helps to see why the two forms behave identically. For a class with constructor parameters, the compiler generates a factory that, while Angular is creating the instance, asks the current injector for each parameter's token and passes the results to `new`. A field initializer that calls `inject()` runs during that same creation, with that same injector current. For a component, that is its element injector, falling back through ancestor elements to the environment injectors. Nothing about lookup rules, scoping or instance sharing changes between the forms — which is why switching styles is a mechanical refactor rather than a behavioural change. ## The rules that come with `inject()` 1. **Injection context only.** `inject()` works while Angular is creating the instance — in field initializers and in the constructor body — and in other injection contexts such as provider factories. Calling it later, in a lifecycle hook or event handler, throws NG0203. 2. **Declaration order.** Field initializers run top to bottom. In ```ts total = this.cart.items().length; // runs first: this.cart is undefined private readonly cart = inject(Cart); ``` the first line fails with a `TypeError`, not a DI error — the context is fine, the field is just not assigned yet. Put injected fields first. 3. **Explicit tokens.** Because the token is an argument, refactors that change a parameter's type no longer silently change what is injected. ## When you still meet constructor injection Most production code written before `inject()` became the norm uses constructor parameters, and it remains fully supported for classes decorated with `@Injectable`, `@Component`, `@Directive` and `@Pipe`. The v22 `@Service()` decorator is designed around `inject()`; its guide lists constructor-based DI as unsupported. You can mix both forms in one class, though a consistent style is easier to read. Converting an existing codebase is a mechanical migration with its own tooling. ## Practical guidance - Default to `inject()` in field initializers, injected fields first. - Keep constructors for logic that needs several injected values together, or drop them entirely. - In base classes, prefer `inject()` so subclasses stay free of DI plumbing. - Treat NG0203 as a sign that `inject()` moved out of construction time, not as a reason to go back to constructor parameters.

  • Is inject() slower or faster than constructor injection at runtime?
    Neither in any way that matters: both end up asking the same injector for the same token while the instance is being created. The choice is about readability, typing and inheritance, which is why the style guide frames its recommendation as style advantages rather than performance.
  • A subclass constructor calls super() and then uses a field the base class set with inject(). Is that safe?
    Yes. Base-class field initializers run during the `super()` call, still inside the injection context of the subclass's creation, so by the time the subclass constructor body continues the base fields are assigned. Only reading a field before its own initializer has run is a problem.

saying these in an interview costs you the question

  • inject() resolves from a different injector than constructor parameters
  • inject() can be called anywhere in a component, including ngOnInit
  • Angular reorders fields so injected ones are always ready first
  • Subclasses must forward the base class's inject() dependencies to super()
  • Constructor injection is removed in current Angular
open as a page

In Angular, where does an injection context exist, and why does calling inject() inside ngOnInit throw NG0203?

level: middleimportance: must knowfreq 60%

basics

~20 s

An injection context exists while Angular creates a DI-managed instance (field initializers, constructor), inside provider and InjectionToken factories, and in functions the framework runs in context, such as guards. ngOnInit runs after construction, when no context is active, so inject() throws NG0203.

open as a page

In Angular, how do you write a reusable injectX() helper function, and what do assertInInjectionContext and runInInjectionContext each add?

level: middleimportance: should knowfreq 32%

basics

~20 s

An injectX() helper is a plain function that calls inject() and must itself be called in an injection context. assertInInjectionContext makes misuse fail with the helper's name; runInInjectionContext lets context-less code call it through a captured injector.

open as a page

An Angular helper function calls inject() inside a setTimeout callback and throws NG0203 when the timer fires; why, and how do you fix it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

An injection context lasts only for the synchronous call that created it; the setTimeout callback runs later on a fresh stack with no current injector. Call inject() before scheduling, or capture an Injector and use runInInjectionContext.

open as a page