Under The Hood
frontend angular Angular 14+ (inject), 16+ (runInInjectionContext)

inject() Function, runInInjectionContext(), and DI Hierarchy

Last updated
Prerequisites:
Angular dependency injection basics (providers, tokens)
Understanding of constructor-based DI
  • angular
  • dependency-injection
  • inject
  • injection-context
  • hierarchy
  • di

Read at your depth

The practical view

inject(SomeToken) returns a dependency from the current injection context. Use it in component/pipe/directive constructors, in field initializers, in providers' factory functions, and in guard/resolver functions. const api = inject(ApiService);. It replaces constructor parameter injection for most cases and works in functions too. If you call inject() outside an injection context (e.g. a random utility function called from an event handler), it throws NG0203. To inject in a context-less function, wrap with runInInjectionContext(environmentInjector, () => inject(...)).

The same idea in other frameworks

react equivalent
React Context via useContext is similar; Angular's inject resolves through the injector hierarchy.

Legacy vs modern

Constructor injection vs inject() in a functional guard

Constructor DI requires class scaffolding; inject() works in plain functions (guards, resolvers, factories) with the same hierarchy resolution.

before → after
Class guard with constructor DI
export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService) {} // ❌ OLD: requires a class + constructor
  canActivate() { return this.auth.isLoggedIn(); }
}
Functional guard with inject()
export const authGuard: CanActivateFn = () => inject(AuthService).isLoggedIn(); // ✅ NEW

Interview gotchas

Context

This tests the ambient-injection-context model — interviewers want to know exactly when DI is available and the standard escape hatches when it isn't.

The mechanical answer

inject() requires an active injection context, which exists while Angular is: constructing components/directives/pipes, executing injectable constructors, running provider factory functions, executing route guards/resolvers/interceptors, and within runInInjectionContext. Calling inject() from an ordinary function invoked later (e.g. an event handler, setTimeout callback, or a utility called from business logic) finds no ambient injector and throws NG0203. Fixes: capture the injected value in an injection-context scope (a component constructor/field initializer) and close over it, or wrap the call site with runInInjectionContext(environmentInjector, () => inject(...)). This is a common migration issue when refactoring class services into functions.

Trap

The naive answer is 'inject() works anywhere because it's global'. It relies on an ambient injector that only exists during DI-driven execution. Another trap: assuming arrow functions in the component body have injection context — they run later (events, timers), so inject() inside them throws. Candidates who describe the ambient injector and runInInjectionContext escape hatch demonstrate real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.