In Angular, how do you write a reusable injectX() helper function, and what do assertInInjectionContext and runInInjectionContext each add?
answer
- helpers inherit the caller's context
- inject prefix signals the rule
- assert: fail fast, name the helper
- run: borrow an injector synchronously
basics
~20 sAn 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 sBecause `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 linesimport { 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
Know that a function called from a field initializer can itself call inject(), and that such helpers are named with an inject prefix.
Write a helper that asserts its context, resolves synchronously and returns plain values, and explain what runInInjectionContext returns.
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.
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