1. What is Dependency Injection in Angular?
What is Dependency Injection?
Dependency Injection (DI) is a design pattern in which an object receives the dependencies it needs from an external system instead of creating those dependencies itself.
Angular has a built-in Dependency Injection framework.
For example, if a Product component needs ProductService, we should not manually create the service:
const service = new ProductService();
Instead, we ask Angular to provide it:
constructor(private productService: ProductService) {
}
Angular creates or retrieves the appropriate ProductService instance and injects it into the component.
Simple idea
Without DI
Product Component
↓
new ProductService()
↓
ProductService
With Angular DI
Product Component
↓
"I need ProductService"
↓
Angular Injector
↓
ProductService instance
↓
Injected into Component
2. Why do we need Dependency Injection?
Consider this component:
export class Product {
private productService = new ProductService();
}
The component is directly responsible for creating ProductService.
This creates tight coupling.
If the service constructor later changes:
constructor(private http: HttpClient) {}
the component would also need to know how to create HttpClient and its dependencies.
DI solves this problem.
constructor(private productService: ProductService) {}
Now the component only says:
"I need a
ProductService."
Angular handles how that service is created.
This creates loose coupling.
3. Full Simple Sample Program
product.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ProductService {
getProducts(): string[] {
return [
'Laptop',
'Mobile',
'Keyboard'
];
}
}
product.ts
import { Component } from '@angular/core';
import { ProductService } from './product.service';
@Component({
selector: 'app-product',
standalone: true,
templateUrl: './product.html'
})
export class Product {
products: string[] = [];
constructor(private productService: ProductService) {
this.products = this.productService.getProducts();
}
}
product.html
<h2>Products</h2>
<ul>
@for (product of products; track product) {
<li>{{ product }}</li>
}
</ul>
4. Program Explanation
Step 1 — Register the Service
@Injectable({
providedIn: 'root'
})
This makes ProductService available through Angular's root-level dependency injection system.
Angular can now create and manage the service.
Step 2 — Request the Dependency
The component requests ProductService:
constructor(private productService: ProductService)
The component doesn't write:
new ProductService();
Instead, Angular handles the creation.
Step 3 — Angular Resolves the Dependency
Angular looks for a provider that can supply:
ProductService
Because we registered:
providedIn: 'root'
Angular knows how to create it.
Step 4 — Angular Injects the Service
Angular provides the service instance to:
private productService: ProductService
The component can then use:
this.productService.getProducts();
5. How does Angular's DI System Work?
Angular DI mainly involves three concepts:
Dependency
Provider
Injector
Dependency
A dependency is something another class requires.
Here:
constructor(private productService: ProductService)
ProductService is the dependency.
Provider
A provider tells Angular how to obtain or create a dependency.
For example:
@Injectable({
providedIn: 'root'
})
registers ProductService with a root-level injector.
Conceptually, Angular has information equivalent to:
When someone asks for ProductService
↓
Create/provide ProductService
Providers can also be explicitly configured:
providers: [ProductService]
Injector
The Injector stores provider configuration, creates/resolves dependencies, and supplies them to consumers.
Conceptually:
Component
│
│ needs ProductService
▼
Injector
│
│ finds provider
▼
ProductService
│
│ creates/reuses instance
▼
Component receives instance
6. Complete DI Flow
Suppose Angular creates:
Product
and sees:
constructor(private productService: ProductService)
The flow is:
1. Angular creates Product component
↓
2. Angular detects ProductService dependency
↓
3. Angular asks the injector for ProductService
↓
4. Injector searches for a matching provider
↓
5. Provider tells Angular how to create ProductService
↓
6. Angular creates ProductService
OR reuses an existing appropriate instance
↓
7. ProductService is injected into Product
↓
8. Product can use ProductService
So remember:
Component
↓ requests
Dependency
↓
Injector
↓ finds
Provider
↓ creates/resolves
Service
↓ injects
Component
7. Dependency Chain
The real power of DI becomes clearer when dependencies have their own dependencies.
Suppose:
Product Component
↓
ProductService
↓
HttpClient
The component only requests:
ProductService
Angular can resolve the entire dependency graph:
Product Component
│
▼
ProductService
│
▼
HttpClient
For example:
@Injectable({
providedIn: 'root'
})
export class ProductService {
constructor(private http: HttpClient) {
}
}
Angular resolves HttpClient before creating ProductService.
This is called dependency resolution.
8. Modern Angular inject() Function
Constructor injection is common:
constructor(private productService: ProductService) {}
Modern Angular also provides the inject() function:
import { Component, inject } from '@angular/core';
import { ProductService } from './product.service';
@Component({
selector: 'app-product',
standalone: true,
templateUrl: './product.html'
})
export class Product {
private productService = inject(ProductService);
products = this.productService.getProducts();
}
Both approaches use Angular's DI system.
Constructor Injection
constructor(private service: ProductService) {}
inject()
private service = inject(ProductService);
inject() is particularly useful in functional APIs such as guards, interceptors, provider factories, and field initializers.
9. Hierarchical Dependency Injection
Angular's DI system is hierarchical.
Services can be provided at different levels.
For example:
Application / Environment Injector
↓
Component
↓
Child Component
If you use:
@Injectable({
providedIn: 'root'
})
the service is available application-wide.
If you provide it directly in a component:
@Component({
selector: 'app-product',
providers: [ProductService]
})
that component gets an instance from its own element injector, and descendants can resolve it from there unless a nearer provider overrides it.
This is important because where you provide a service affects its scope and instance sharing.
10. Advantages of Dependency Injection
Loose Coupling
The component doesn't need to know how the service is constructed.
Component → asks for interface/token/class
Angular DI → handles creation
Reusability
The same service can be injected into multiple components.
Component A ─┐
Component B ─┼── ProductService
Component C ─┘
Easier Testing
A real dependency can be replaced with a test implementation or mock.
Centralized Dependency Management
Angular manages dependency creation and resolution.
Maintainability
Changing how a service is constructed usually doesn't require changing every consumer.
Dependency Chaining
Angular can automatically resolve dependencies of dependencies.
11. Disadvantages
Learning Curve
Concepts such as:
- Providers
- Injection tokens
- Injector hierarchy
- Provider scopes
can initially be confusing.
Incorrect Provider Scope
Providing the same service at multiple levels can accidentally create multiple service instances.
Hidden Dependency Resolution
In large applications, complex dependency graphs can make it harder to understand exactly where a dependency originates.
12. Key Points
- DI = Dependency Injection.
- Angular has a built-in DI framework.
- A dependency is an object/class another object requires.
- A provider tells Angular how to supply a dependency.
- An injector resolves and manages dependencies.
providedIn: 'root'commonly provides an application-wide service.- Components should generally request dependencies instead of manually creating them.
- Angular supports both constructor injection and
inject(). - Angular DI is hierarchical.
- Different provider locations can result in different service instances.
- DI promotes loose coupling, maintainability, reuse, and testability.
13. Interview Questions and Answers
1. What is Dependency Injection in Angular?
Dependency Injection is a design pattern where Angular provides the dependencies required by a class instead of the class manually creating those dependencies.
2. Why do we need DI?
DI reduces tight coupling, improves code reuse, makes applications easier to maintain, and makes dependencies easier to replace during testing.
3. What are the main parts of Angular DI?
The main concepts are:
Dependency
Provider
Injector
Injection Token
4. What is an Injector?
An injector is responsible for finding providers, creating or retrieving dependency instances, and supplying those dependencies to consumers.
5. What is a Provider?
A provider tells Angular how to create or obtain a dependency.
For example:
providers: [ProductService]
6. What does providedIn: 'root' mean?
@Injectable({
providedIn: 'root'
})
It makes the service available through the application's root-level DI environment and normally gives you a shared application-wide instance.
7. Constructor injection vs inject()?
Constructor injection:
constructor(private service: ProductService) {}
Modern inject():
private service = inject(ProductService);
Both resolve dependencies using Angular's DI system.
8. What is hierarchical DI?
Angular maintains injectors at different levels. A dependency can therefore be provided application-wide or more locally, such as at a component level.
9. What happens if Angular cannot find a provider?
Angular fails dependency resolution and typically throws a NullInjectorError indicating that no provider was found for the requested dependency.
Interview Definition
Dependency Injection in Angular is a mechanism where Angular's injector creates or retrieves dependencies based on registered providers and supplies them to components, services, directives, and other consumers, reducing tight coupling and improving maintainability and testability.