Angular Dependency Injection (DI)
Learn Angular Dependency Injection (DI) in detail including Injector hierarchy, providers, inject(), InjectionToken, hierarchical DI, standalone APIs, architecture diagrams, lifecycle, best practices, enterprise examples, and interview questions.
Dependency Injection (DI) is one of Angular's core architectural features. It enables Angular to automatically create and supply the objects (dependencies) that a class requires instead of having the class create them itself.
Without DI, components become tightly coupled to concrete implementations. With DI, applications become:
- Loosely coupled
- Easier to test
- Easier to maintain
- More reusable
- Highly scalable
Dependency Injection is one of the most frequently asked Angular interview topics, especially for Senior Developers, Tech Leads, and Architects.
Table of Contents
- What is Dependency Injection?
- Why Dependency Injection?
- Inversion of Control (IoC)
- DI Architecture
- Injectable Services
- Providers
- Injector Hierarchy
- inject() Function
- InjectionToken
- Multi Providers
- Hierarchical Dependency Injection
- Standalone Applications
- Enterprise Example
- Best Practices
- Top 10 Interview Questions
- Summary
What is Dependency Injection?
Dependency Injection is a design pattern where an external object provides the dependencies needed by a class.
Instead of this:
export class UserComponent {
service = new UserService();
}
Angular does this automatically:
constructor(
private userService: UserService
) {}
Angular creates the service and injects it.
Why Dependency Injection?
Without DI
- Tight coupling
- Difficult testing
- Duplicate objects
- Hard to replace implementations
With DI
- Loose coupling
- Easier unit testing
- Singleton services
- Better maintainability
- Better scalability
Dependency Injection Architecture
flowchart LR
Component
Component --> Injector
Injector --> UserService
Injector --> LoggerService
Injector --> ConfigService
UserService --> API
LoggerService --> Console
ConfigService --> Environment
Dependency Injection Flow
sequenceDiagram
participant Component
participant Injector
participant Service
participant API
Component->>Injector: Need UserService
Injector->>Service: Create or Retrieve Instance
Service->>API: Fetch Data
API-->>Service: Response
Service-->>Component: Data
What is Inversion of Control (IoC)?
Normally, a class creates its own dependencies.
const service = new UserService();
With IoC
Angular Framework
↓
Injector
↓
Creates Service
↓
Provides Service
↓
Component
The control of object creation is inverted and managed by Angular.
Service Example
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class UserService {
getUsers() {
return ['Venu', 'John', 'David'];
}
}
Injecting a Service
import { Component } from '@angular/core';
@Component({
selector: 'app-users',
template: `
<ul>
<li *ngFor="let user of users">
{{ user }}
</li>
</ul>
`
})
export class UsersComponent {
users = this.userService.getUsers();
constructor(
private userService: UserService
) {}
}
Service Creation Flow
flowchart TD
Component
Component --> Constructor
Constructor --> Injector
Injector --> UserService
UserService --> Component
@Injectable Decorator
The @Injectable() decorator tells Angular that a class can participate in Dependency Injection.
@Injectable({
providedIn: 'root'
})
export class ProductService {}
Benefits
- Registers the service
- Enables dependency injection
- Supports tree shaking
providedIn Options
| Option | Scope |
|---|---|
'root' |
Singleton application-wide service |
'platform' |
Shared across Angular applications on the same page |
'any' |
New instance for each lazy-loaded environment (legacy option; use with care) |
| Specific Injector | Register with a specific injector or environment |
Provider Registration
Angular allows providers at multiple levels.
flowchart TD
Application
Application --> RootInjector
RootInjector --> FeatureInjector
FeatureInjector --> ComponentInjector
ComponentInjector --> Component
Root Provider
@Injectable({
providedIn: 'root'
})
export class AuthService {}
One shared instance for the application.
Component Provider
@Component({
selector: 'app-user',
providers: [UserService]
})
export class UserComponent {}
Every component instance receives its own UserService.
Provider Hierarchy
flowchart TD
RootInjector
RootInjector --> FeatureInjector
FeatureInjector --> ComponentInjector
ComponentInjector --> DirectiveInjector
Angular resolves dependencies by searching from the closest injector upward.
Injector Hierarchy Resolution
flowchart LR
Component
Component --> ComponentInjector
ComponentInjector --> Found
ComponentInjector --> RootInjector
RootInjector --> FoundService
Constructor Injection
The most common approach.
constructor(
private authService: AuthService,
private logger: LoggerService
){}
Angular injects both services automatically.
Using inject() Function
Angular also supports the inject() function.
import { inject } from '@angular/core';
@Component({
standalone: true,
template: `{{ users.length }}`
})
export class UserComponent {
private userService = inject(UserService);
users = this.userService.getUsers();
}
Benefits
- Cleaner syntax
- No constructor required
- Useful in standalone APIs
- Convenient for functional patterns
inject() Flow
flowchart LR
A["inject()"]
B["Injector"]
C["Service"]
D["Component"]
A --> B
B --> C
C --> D
InjectionToken
Used when injecting interfaces, configuration objects, or primitive values.
import { InjectionToken } from '@angular/core';
export const API_URL =
new InjectionToken<string>('API_URL');
Provider
{
provide: API_URL,
useValue: 'https://api.company.com'
}
Injection
import { Inject } from '@angular/core';
constructor(
@Inject(API_URL)
private apiUrl: string
) {}
Why InjectionToken?
Interfaces disappear after TypeScript compilation.
interface PaymentService {}
Angular cannot inject interfaces directly.
Instead
const PAYMENT_SERVICE =
new InjectionToken<PaymentService>(
'PaymentService'
);
Provider Types
| Provider | Purpose |
|---|---|
| useClass | Create an instance of a class |
| useValue | Inject a constant value |
| useExisting | Alias another provider |
| useFactory | Create dynamically |
| multi | Multiple implementations |
useClass Example
providers: [
{
provide: LoggerService,
useClass: ConsoleLoggerService
}
]
useValue Example
providers: [
{
provide: API_URL,
useValue: 'https://api.company.com'
}
]
useFactory Example
export function loggerFactory() {
return new LoggerService();
}
providers: [
{
provide: LoggerService,
useFactory: loggerFactory
}
]
useExisting Example
providers: [
{
provide: OldLogger,
useExisting: LoggerService
}
]
Multi Providers
Multiple services can share the same token.
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: LoggingInterceptor,
multi: true
}
]
Angular injects an array of interceptors.
Multi Provider Architecture
flowchart LR
Injector
Injector --> AuthInterceptor
Injector --> LoggingInterceptor
Injector --> CacheInterceptor
AuthInterceptor --> HTTPRequest
LoggingInterceptor --> HTTPRequest
CacheInterceptor --> HTTPRequest
Hierarchical Dependency Injection
Angular maintains a hierarchy of injectors.
flowchart TD
RootInjector
RootInjector --> AdminModule
RootInjector --> UserModule
AdminModule --> AdminComponent
UserModule --> ProfileComponent
Benefits
- Scoped services
- Better memory usage
- Independent feature modules
- Easier testing
Standalone Application Example
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient()
]
});
Modern Angular encourages provider registration during application bootstrap for standalone applications.
Enterprise Banking Example
flowchart TD
Dashboard
Dashboard --> AccountService
Dashboard --> AuthService
Dashboard --> NotificationService
Dashboard --> LoggerService
AccountService --> RESTAPI
AuthService --> OAuth
NotificationService --> Email
LoggerService --> Monitoring
Component
constructor(
private accountService: AccountService,
private authService: AuthService,
private logger: LoggerService
){}
Performance Best Practices
| Practice | Benefit |
|---|---|
Use providedIn: 'root' |
Tree shaking and singleton instances |
| Avoid unnecessary component providers | Reduce duplicate instances |
Use inject() where appropriate |
Cleaner standalone code |
Prefer interfaces with InjectionToken |
Better abstraction |
| Use factories for runtime configuration | Flexible initialization |
| Scope services intentionally | Better resource management |
Common Mistakes
Creating Services with new
❌ Avoid
const service = new UserService();
Always let Angular manage service creation.
Registering Services Everywhere
Registering the same service in multiple component providers may create unnecessary instances.
Injecting Interfaces Directly
Interfaces are not available at runtime.
Use InjectionToken instead.
Circular Dependencies
Avoid services that depend on each other directly.
A
↓
B
↓
A
Refactor shared logic into another service if needed.
Advantages
| Feature | Benefit |
|---|---|
| Loose Coupling | Easier maintenance |
| Testability | Simple mocking |
| Singleton Support | Shared instances |
| Hierarchical Injectors | Scoped services |
| Tree Shaking | Smaller bundles |
| Standalone APIs | Modern Angular development |
Top 10 Angular Dependency Injection Interview Questions
1. What is Dependency Injection?
Answer
Dependency Injection is a design pattern where Angular creates and supplies required dependencies instead of allowing classes to create them directly.
2. Why is Dependency Injection important?
Answer
It promotes loose coupling, improves maintainability, simplifies testing, and enables reusable services.
3. What is the purpose of @Injectable()?
Answer
@Injectable() marks a class as available for Angular's Dependency Injection system and allows it to declare injectable dependencies.
4. What does providedIn: 'root' mean?
Answer
It registers the service with the application's root injector, creating a singleton instance shared throughout the application.
5. What is an Injector?
Answer
An Injector is responsible for creating, storing, and supplying service instances to components, directives, pipes, and other services.
6. What is the difference between constructor injection and inject()?
| Constructor Injection | inject() |
|---|---|
| Uses constructor parameters | Uses the inject() function |
| Traditional approach | Useful in standalone APIs and functional code |
| Easy to understand | Reduces constructor boilerplate in some cases |
7. What is an InjectionToken?
Answer
An InjectionToken provides a runtime token for injecting values, interfaces, or configuration objects that cannot be identified by class type alone.
8. What are the different provider types?
Answer
useClassuseValueuseExistinguseFactorymulti
9. What are Multi Providers?
Answer
Multi providers allow multiple values or implementations to be associated with the same injection token. A common example is HTTP_INTERCEPTORS.
10. What are the best practices for Dependency Injection?
Answer
- Prefer
providedIn: 'root'for application-wide services. - Avoid manually creating services with
new. - Use
InjectionTokenfor interfaces and configuration. - Scope providers intentionally.
- Use
inject()where it improves readability. - Avoid circular dependencies.
- Register providers only where appropriate.
Interview Cheat Sheet
| Topic | Remember |
|---|---|
| DI | Angular supplies dependencies |
| Main Decorator | @Injectable() |
| Root Provider | providedIn: 'root' |
| Modern API | inject() |
| Runtime Token | InjectionToken |
| Provider Types | useClass, useValue, useExisting, useFactory, multi |
| Injector | Creates and provides services |
| Singleton | Root Injector |
| Component Scope | Component providers |
| Multi Provider Example | HTTP_INTERCEPTORS |
Summary
Dependency Injection is a foundational concept in Angular that enables loosely coupled, maintainable, and testable applications. By using Angular's hierarchical injector system, services can be shared globally or scoped to specific features and components. Modern Angular enhances DI with features like inject(), InjectionToken, standalone application providers, and tree-shakable services, making Dependency Injection one of the most powerful features of the framework.
Key Takeaways
- ✔ Dependency Injection promotes loose coupling.
- ✔ Angular manages object creation through Injectors.
- ✔
@Injectable()enables classes to participate in DI. - ✔
providedIn: 'root'creates application-wide singleton services. - ✔ Use
inject()in modern Angular where appropriate. - ✔ Use
InjectionTokenfor interfaces and configuration values. - ✔ Understand provider types:
useClass,useValue,useExisting,useFactory, andmulti. - ✔ Leverage hierarchical injectors for proper service scoping.
- ✔ Avoid manual service creation and circular dependencies.
- ✔ Dependency Injection is one of the most important Angular interview topics.
Next Article
➡️ 32-Service-Lifecycle-QA.md