Open-Source Wikis

/

Angular

/

Systems

/

Dependency injection

angular/angular

Dependency injection

The hierarchical injector tree. Two complementary lookup paths: an "environment" injector for application-scoped providers (provideRouter, provideHttpClient, provideAnimations, ...) and an "element" injector for per-component DI (@Component({providers: [...]}) ).

Mental model

Every Angular instance lives in two injector trees at once:

  1. The environment injector tree — coarse-grained, shared by many components. Providers configured at the application or feature level.
  2. The element injector tree — fine-grained, one node per directive instance. Resolves directives, component-level providers, host directives.

Lookup walks element-first, then falls back to environment. Each level can have its own providers.

graph TD
  Platform["PlatformInjector"] --> Env["EnvironmentInjector<br/>(application-level)"]
  Env --> SubEnv["EnvironmentInjector<br/>(lazy route)"]
  Env --> RootEl["Root element injector<br/>(root component)"]
  RootEl --> ChildEl["Child element injector<br/>(child component)"]
  ChildEl --> GrandchildEl["..."]

A token is resolved by walking from the active element injector up through its parents, then through the active environment injector chain to the platform.

Environment injectors

Environment providers are configured via bootstrapApplication's providers, provideRouter's feature functions, EnvironmentInjector.create, etc. Internally they form a chain rooted at the platform.

EnvironmentProviders is the marker type returned by provideXxx functions. It distinguishes a feature-style provider bundle from an ordinary Provider. Trying to put EnvironmentProviders into a component's providers array is a compile-time error.

Element injectors

Per-DOM-element. Implemented in packages/core/src/render3/di.ts.

The element injector uses a bloom filter to short-circuit token lookups. Each TNode carries:

  • A small bitset (the bloom filter) listing every directive token visible at that element.
  • An array slot for each directive instance.

A token lookup hashes the token, checks the bloom filter for a possible match, walks the directive list when the bloom hits, and falls back to the parent injector when it doesn't. The bloom filter avoids per-element linear scans for the common case where a token isn't provided.

graph LR
  Token --> Hash["hash(token)"]
  Hash --> Bloom["TNode.directiveBloom"]
  Bloom -->|miss| Parent["walk to parent TNode"]
  Bloom -->|possible hit| Directives["TNode directiveStartIndex..directiveEnd"]
  Directives -->|exact match| Instance["LView slot lookup"]
  Directives -->|no match| Parent
  Parent --> Env["fall back to EnvironmentInjector"]

This is one of the hottest code paths in Angular. Performance changes here go through packages/core/src/render3/PERF_NOTES.md review.

inject()

The function form of constructor injection (packages/core/src/di/inject.ts) reads from whichever injector is currently active. It's valid in:

  • Constructor bodies (during construction, the element injector is current).
  • @Component/@Directive/@Pipe field initializers.
  • DI factory functions (useFactory).
  • provideXxx feature functions (during environment injector setup).
  • The body of runInInjectionContext(injector, () => …).

Calling inject() outside an injection context throws a RuntimeError. The "in injection context" predicate is enforced via a thread-local-like marker in _currentInjector.

Provider scopes

Three scopes:

Scope Where to declare Lifetime
providedIn: 'root' @Injectable decoration Application lifetime, lazy-instantiated.
providedIn: SomeFeatureToken @Injectable decoration with a feature scope token Until the feature is destroyed.
Component providers @Component({providers: [...]}) Until the component instance is destroyed.
Environment providers provideXxx returns Until the environment injector is destroyed (typically app lifetime).

providedIn: 'root' is the default for new code — it's tree-shakable (the provider is dropped if the token is never injected) and scope-correct in standalone apps.

Standalone import dependency injection

When a standalone component declares imports: [SomeOtherStandaloneComponent], the framework constructs a synthetic environment scope so that the imported component's providers (if any) are visible. The scope is computed during ngtsc's resolve phase and emitted into the ɵcmp definition.

Multi-providers

Providers with multi: true accumulate into an array under the same token. Common pattern for things like HTTP_INTERCEPTORS, APP_INITIALIZER, route guards. The runtime concatenates contributions from every level of the chain that supplies the token.

Integration points

  • @angular/core — owns the implementation.
  • @angular/router — uses EnvironmentInjector.create to scope per-route providers (Route.providers).
  • Standalone bootstrapbootstrapApplication's providers flow into the root environment injector.
  • Test harnessTestBed reuses the same machinery; it just wires in providers from the configureTestingModule call instead of bootstrapApplication.

Where to start when modifying

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Dependency injection – Angular wiki | Factory