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:
- The environment injector tree — coarse-grained, shared by many components. Providers configured at the application or feature level.
- 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.
packages/core/src/di/r3_injector.ts— theR3Injectorimplementation.packages/core/src/application/create_application.ts— wiring up the application's environment injector at boot.EnvironmentInjectoris the public token;R3Injectoris the implementation.
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/@Pipefield initializers.- DI factory functions (
useFactory). provideXxxfeature 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— usesEnvironmentInjector.createto scope per-route providers (Route.providers).- Standalone bootstrap —
bootstrapApplication'sprovidersflow into the root environment injector. - Test harness —
TestBedreuses the same machinery; it just wires in providers from theconfigureTestingModulecall instead ofbootstrapApplication.
Where to start when modifying
- A new injector flag (e.g., a new lookup behavior): see
packages/core/src/di/interface/injector.tsfor the existingInjectFlags/InjectOptionsandr3_injector.tsfor the implementation. - An
inject()extension: extendpackages/core/src/di/inject.ts, update the public API golden. - An element-injector hot-path change: read
packages/core/src/render3/di.tscarefully and run the directive-DI benchmarks before and after; this code is allergic to allocations.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.