skip to content

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

level: middleimportance: should knowfreq 32%

answer

  1. helpers inherit the caller's context
  2. inject prefix signals the rule
  3. assert: fail fast, name the helper
  4. run: borrow an injector synchronously

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.

solid answer

~40 s

Because `inject()` uses the caller's context, any function called synchronously from a field initializer, constructor or factory can call it too. That enables composable helpers such as `injectPageTitle('Shop')`, which injects `Title` and returns a setter. The `inject` prefix is the convention that tells callers "call me at construction time". Start the helper with `assertInInjectionContext(injectPageTitle)`: outside a context it throws NG0203 with a message beginning `injectPageTitle() can only be used within an injection context`, which points at the misuse rather than at an internal `inject()`. When code that has no context needs the helper — a method, a callback — capture `inject(Injector)` in a field and call `runInInjectionContext(this.injector, () => injectPageTitle('Shop'))`, which runs the function synchronously with that injector current and returns its result.

code

ts · 27 lines
ts
import { Component, Injector, assertInInjectionContext, inject, runInInjectionContext } from '@angular/core';
import { Title } from '@angular/platform-browser';

export function injectPageTitle(prefix: string): (page: string) => void {
  assertInInjectionContext(injectPageTitle);
  const title = inject(Title);
  return (page) => title.setTitle(`${prefix} · ${page}`);
}

@Component({
  selector: 'app-product-list',
  template: `<button (click)="onCategoryLoaded('Garden')">Load</button>`,
})
export class ProductList {
  private readonly injector = inject(Injector);
  private readonly setShopTitle = injectPageTitle('Shop'); // in context

  constructor() {
    this.setShopTitle('All products');
  }

  onCategoryLoaded(category: string): void {
    // injectPageTitle(category); // would throw NG0203 naming injectPageTitle
    const setTitle = runInInjectionContext(this.injector, () => injectPageTitle(category));
    setTitle('Overview');
  }
}

go deeper

for a junior

Know that a function called from a field initializer can itself call inject(), and that such helpers are named with an inject prefix.

for a middle

Write a helper that asserts its context, resolves synchronously and returns plain values, and explain what runInInjectionContext returns.

for a senior

Design helper APIs that stay usable outside construction via an explicit injector, and know the v16 replacement of runInContext and the NG0205 destroyed-injector case.

for a principal

Curate a shared set of inject helpers as the team's composition layer, with clear context contracts and review rules against hidden injector capture.

## Helpers that inject `inject()` does not care which function calls it, only whether an **injection context** is active. So a plain function called synchronously from a field initializer, constructor or provider factory can call `inject()` itself. That makes DI composable: repeated setup can move out of components into small functions. ```ts export function injectPageTitle(prefix: string) { assertInInjectionContext(injectPageTitle); const title = inject(Title); return (page: string) => title.setTitle(`${prefix} · ${page}`); } export class ProductList { private readonly setTitle = injectPageTitle('Shop'); // field initializer: in context } ``` Angular's own guide shows the same shape with `injectNativeElement()`, which asserts the context and returns `inject(ElementRef).nativeElement`. ## Conventions for a good helper 1. **Name it `injectX`.** The prefix signals that it follows the same rule as `inject()`: call it during construction or in another context. The convention exists for readers; Angular does not enforce it. 2. **Resolve everything synchronously at the top**, then return values, functions or signals that can be used later without DI. 3. **Assert first.** The first line is `assertInInjectionContext(injectX)`. 4. **Accept an explicit injector when callers may lack a context.** An optional `injector?: Injector` parameter lets advanced callers pass one and wrap the body in `runInInjectionContext` themselves. 5. **Keep it free of lifecycle surprises.** If the helper registers cleanup, do it with services that are themselves resolved in context. ## What `assertInInjectionContext` adds Without it, a helper called from the wrong place fails at its internal `inject(Title)` with a message about the `Title` token — true but misleading, because the mistake is the call site. `assertInInjectionContext(fn)` checks for an active context and, when there is none, throws NG0203 with a message built from the function you passed: "injectPageTitle() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`". You pass the **function reference**, not a string. Angular's source explains why: taking a `Function` rather than a name avoids keeping an unminified name string in the bundle regardless of minification. The message is built in development mode; production builds still throw NG0203, without the long text. ## What `runInInjectionContext` adds `runInInjectionContext(injector, fn)`: - sets `injector` as current, runs `fn` synchronously, restores the previous state and **returns `fn`'s return value**; - accepts any `Injector` — an environment injector, or an element injector obtained with `inject(Injector)` inside a component; - throws NG0205 if given an environment injector that has already been destroyed; - does not extend to asynchronous work started inside `fn`. It replaced the older `EnvironmentInjector.runInContext` method in v16, which is deprecated; `assertInInjectionContext` arrived in the same release. | API | Purpose | Returns | |---|---|---| | `inject(token)` | resolve a token from the current context | the value | | `assertInInjectionContext(fn)` | fail fast with `fn`'s name when no context is active | nothing | | `runInInjectionContext(injector, fn)` | create a context from a captured injector for `fn` | `fn`'s result | ## Using a helper from a method When a method must call an `injectX` helper — say the title prefix depends on data loaded after construction — capture the injector early: ```ts private readonly injector = inject(Injector); onCategoryLoaded(name: string) { const setTitle = runInInjectionContext(this.injector, () => injectPageTitle(name)); setTitle('Overview'); } ``` This is the escape hatch, not the default. If the helper can be called at construction time with the dynamic part passed later as an argument, prefer that design: it keeps the dependency visible where the class is created. ## Common mistakes - **Returning a function that calls `inject()` later.** `return () => inject(Title).setTitle(...)` defers the lookup to call time, which is usually outside any context. Resolve first, then return the closure. - **Calling the helper in a hook.** `ngOnInit() { this.setTitle = injectPageTitle('Shop'); }` fails; the assertion makes that failure name the helper. - **Capturing a module-level injector.** Storing an injector in a global variable so helpers can use it anywhere hides dependencies and breaks when several apps or tests share a page. - **Expecting `runInInjectionContext` to cover async work** started inside it; only the synchronous part runs in context. ## Testing helpers Helpers that call `inject()` are easy to test once something provides a context; the testing utilities offer their own wrapper for that, so a helper can be exercised without creating a component.

  • Why pass the function itself to assertInInjectionContext instead of its name as a string?
    The error message is built from the function's name in development mode, and taking a `Function` means no separate name string has to stay in the production bundle. It also survives renames: refactoring the helper updates the reference automatically.
  • Can runInInjectionContext use a component's element injector, or only environment injectors?
    Any `Injector` works. Inside a component, `inject(Injector)` returns the element injector, and running a helper with it resolves tokens the way the component would, including providers from ancestor components. Environment injectors are checked for having been destroyed; a destroyed one makes the call throw NG0205.

saying these in an interview costs you the question

  • Only Angular's own APIs are allowed to call inject()
  • The inject prefix is required by the compiler for helpers
  • assertInInjectionContext returns false so the helper can branch
  • runInInjectionContext keeps the context for async work inside it
  • runInInjectionContext only accepts the root environment injector