All topics
Servicesadvanced

Hierarchical Injectors

Explain how Angular's injector tree mirrors the component tree and how that affects instance sharing and overriding.

Angular doesn't have one single injector for the whole application — it has a tree of injectors that roughly mirrors the component tree, plus a root/platform-level injector at the top, and understanding this hierarchy is what separates a surface-level understanding of DI from a deep one. Every component (and every NgModule, in the older model) can optionally have its own injector by declaring providers in its metadata, creating a new node in that tree.

It's like a company's approval chain — a request goes to your immediate manager first, and only escalates to their boss if your manager can't approve it; a department that has its own approved budget for something never needs to escalate that particular request to headquarters at all.

Key Concepts

1
When a class asks for a dependency, Angular starts at the injector closest to where the request originated and walks upward through ancestor injectors until it finds a matching provider, stopping at the first match. This means a provider registered on a component is only visible to that component and its descendants, not to siblings or ancestors, and — crucially — a provider registered lower in the tree shadows (overrides) one registered higher up, without needing to change any code higher in the tree.
2
This is exactly how you get a "scoped singleton" — a service that behaves like a singleton within a specific component subtree (e.g., one ShoppingCartService instance per checkout flow, but a different instance if the checkout flow is opened twice on different routes) rather than a true app-wide singleton. Each place providers: [ShoppingCartService] appears creates a fresh instance local to that component and its children.
ShoppingCartServiceproviders: [ShoppingCartService]
3
A strong interview answer connects this to providedIn: 'root' as the common case (register once at the root injector, get a true app-wide singleton) versus explicit component-level providers arrays as the escape hatch for scoped instances, and mentions that this hierarchy is also why the resolution modifiers (@Optional, @Self, @SkipSelf, @Host) exist — they let a class control exactly how far up (or whether at all) the tree search should go.
providedIn: 'root'providers@Optional@Self@SkipSelf