What is dependency injection in Angular?
Dependency Injection (DI) is a core design pattern used in Angular to manage how components and services obtain the dependencies they need. It's a way of decoupling the creation of objects from their usage, making applications more modular, testable, and maintainable.
What is Dependency Injection?
In its essence, Dependency Injection is a software design pattern that deals with how components get hold of their dependencies. Instead of a component creating its dependencies, an 'injector' provides them. This inversion of control separates the responsibility of creating an object from the responsibility of using it.
How Angular Implements DI
Angular has its own sophisticated DI system. It relies on three main concepts: Injectors, Providers, and the @Injectable() decorator. When Angular creates a component, it first checks the constructor for any dependencies. If found, it consults its injector to find a 'provider' that can supply an instance of that dependency.
Providers
A provider is a recipe that tells an injector how to create or obtain a dependency. It maps a token (usually a class type) to a factory function that produces the dependency's value. Providers are configured at different levels: component, module, or application-wide (using providedIn: 'root' for tree-shakable services).
Injectors
An injector is an object that can create dependency instances and inject them into other objects. Angular maintains a hierarchical tree of injectors. Each component has its own injector, and these injectors inherit from their parent's injector. This hierarchy allows for different instances of services to be provided at different levels of the application.
Key Benefits
- Testability: Easier to mock dependencies when testing components in isolation.
- Maintainability: Components become less coupled, making code easier to change and understand.
- Reusability: Services can be reused across multiple components without tight coupling.
- Scalability: Helps manage complex applications by breaking them into manageable, interconnected parts.
Example Usage
Here's a simple example showing a LoggerService being injected into a MyComponent. The @Injectable() decorator marks LoggerService as a class that can be injected, and providedIn: 'root' makes it a singleton available throughout the application.
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class LoggerService {
log(message: string) {
console.log(`[Logger]: ${message}`);
}
}
import { Component } from '@angular/core';
import { LoggerService } from './logger.service';
@Component({
selector: 'app-my',
template: '<button (click)="logMessage()">Log Something</button>'
})
export class MyComponent {
constructor(private logger: LoggerService) {}
logMessage() {
this.logger.log('Hello from MyComponent!');
}
}