What is dependency injection in Angular and how does the injector hierarchy work?
Angular dependency injection explained: how @Injectable, providers and the injector hierarchy give you singletons, scoped services and testable code.
Expected Interview Answer
Dependency injection (DI) in Angular is a design pattern where a class receives the objects it depends on from an external injector rather than creating them itself, and Angular resolves those dependencies through a hierarchy of injectors.
You mark a class with @Injectable and register it as a provider, then declare it as a constructor parameter; Angular's injector looks up a matching provider and supplies the instance. Angular maintains a tree of injectors — the root (platform and application) injector plus per-component element injectors — and resolution walks up from the component's element injector toward the root until a provider is found. Where you register a provider (root, a module, or a component) determines its scope and how many instances exist.
- Decouples classes from how their dependencies are constructed
- Makes services easy to mock and unit test
- Enables singleton sharing via providedIn: 'root'
- Supports scoped instances per component subtree
- Centralizes configuration through provider tokens
AI Mentor Explanation
Think of a batter walking out to the middle: they never make their own bat, gloves, or helmet. The team kit manager hands them exactly the gear registered for their role. Angular's injector is that kit manager, and if the local dressing room has no spare pads it asks the pavilion store above it, walking up the ground's supply chain until the right kit is found.
Step-by-Step Explanation
Step 1
Make a service injectable
Decorate the class with @Injectable, typically using providedIn: 'root' so Angular can tree-shake and register it as an application-wide singleton.
Step 2
Declare the dependency
Add the service as a typed constructor parameter (or use the inject() function) in the component or service that needs it.
Step 3
Angular reads the token
At instantiation Angular uses the parameter type as a provider token and starts resolution at the consumer's injector.
Step 4
Walk the injector tree
The element injector is checked first; if no provider matches, resolution climbs to parent element injectors, then the module/root injector, until a provider is found.
Step 5
Instantiate and cache
The first resolving injector creates the instance (respecting its scope) and caches it, so subsequent requests at that level reuse the same object.
What Interviewer Expects
- Clear definition of DI as inversion of construction control
- Understanding of @Injectable and providedIn: 'root'
- Knowledge that Angular has a hierarchical injector tree
- How resolution walks from element injector up to root
- How provider placement controls singleton vs scoped instances
Common Mistakes
- Confusing DI with simply importing a class
- Instantiating services with new instead of injecting them
- Assuming every provider is always a global singleton
- Not understanding that component-level providers create new instances
- Forgetting @Injectable on a service that itself has dependencies
Best Answer (HR Friendly)
“Dependency injection means Angular gives a class the helper objects it needs instead of the class building them itself. Angular keeps an organized hierarchy of providers, so when a component asks for a service it searches from the closest level upward until it finds the right one.”
Code Example
@Injectable({ providedIn: 'root' })
export class LoggerService {
log(message: string) {
console.log('[Logger]', message);
}
}
@Component({ selector: 'app-dashboard', template: '' })
export class DashboardComponent {
constructor(private logger: LoggerService) {}
ngOnInit() {
this.logger.log('Dashboard initialized');
}
}Follow-up Questions
- What is the difference between providedIn: 'root' and registering in a component's providers array?
- How does the inject() function differ from constructor injection?
- What are injection tokens and when do you need one?
- How do useClass, useValue, useFactory and useExisting providers differ?
- What happens if a dependency cannot be resolved anywhere in the tree?
MCQ Practice
1. Where does Angular begin resolving a component's dependency?
Resolution starts at the consumer's element injector and walks up the tree toward the root until a matching provider is found.
2. What does providedIn: 'root' typically create?
providedIn: 'root' registers the service on the root injector, giving a single shared instance across the application.
3. What happens if you list a service in a component's providers array?
A component-level provider creates a scoped instance for that component's subtree, distinct from the root singleton.
Flash Cards
What is dependency injection? — A pattern where a class receives its dependencies from an injector instead of constructing them itself.
What does @Injectable do? — Marks a class as available for DI and lets it declare where it is provided, e.g. providedIn: 'root'.
How does the injector hierarchy resolve a token? — It starts at the element injector and walks up to parent and root injectors until a matching provider is found.
How do you get a scoped (non-singleton) service? — Register it in a component's providers array so a new instance is created for that component subtree.