Dependecy Injection and Angular
In Angular:
Angular uses DI extensively to manage services. When a component or service needs a dependency, Angular’s DI system injects it at runtime.
@Injectable({
providedIn: 'root',
})
export class UserService {
constructor(private http: HttpClient) {}
}
In this example, HttpClient
is injected into UserService
by Angular's DI system.
In Angular, Dependency Injection (DI) is a core concept that is deeply integrated into the framework, making it easy to manage and inject dependencies throughout your application. Here’s a detailed guide on how to implement and use DI in Angular.
1. Understanding Angular’s DI System
- Injector: Angular’s DI system is built around the concept of an injector. The injector is responsible for creating instances of services and injecting them into components or other services where needed.
- Providers: A provider tells the injector how to create a service. Angular provides different ways to configure providers, which will be discussed below.
2. Creating and Providing a Service
- To create a service, you typically use the Angular CLI:
ng generate service example
.
// example.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root', // This makes the service a singleton and available throughout the app
})
export class ExampleService {
constructor() { }
getData() {
return 'Hello from ExampleService';
}
}
3. Injecting the Service into a Component
- Once the service is provided, you can inject it into any component or another service using Angular’s constructor injection.
// example.component.ts
import { Component, OnInit } from '@angular/core';
import { ExampleService } from './example.service';
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css'],
})
export class ExampleComponent implements OnInit {
message: string;
// The service is injected here
constructor(private exampleService: ExampleService) {}
ngOnInit(): void {
this.message = this.exampleService.getData();
}
}
4. Understanding Providers in Angular
providedIn: 'root'
: This is the most common and recommended way to provide a service. It ensures that the service is a singleton and available throughout the application.- Component-Level Providers: You can also provide a service at the component level. This creates a new instance of the service for that component and its children.
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css'],
providers: [ExampleService], // New instance for this component and its children
})
export class ChildComponent {
constructor(private exampleService: ExampleService) {}
}
- Module-Level Providers: You can provide services at the module level by adding them to the
providers
array in the module’s metadata. This is useful for shared modules that need to provide specific services.
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [ExampleService], // Provided at the module level
bootstrap: [AppComponent],
})
export class AppModule {}
5. Hierarchical Injection
- Angular’s DI system is hierarchical. If a service is provided at the root level, it’s shared across the application. If a service is provided at the component or module level, Angular creates a new instance of the service within that scope, which allows for more granular control over the service’s lifetime.
6. Advanced DI Concepts
- Factory Providers: Sometimes, you need to configure the way a service is created. You can use a factory function to do this.
export function exampleFactory() {
return new ExampleService();
}
@NgModule({
providers: [
{ provide: ExampleService, useFactory: exampleFactory }
],
})
export class AppModule {}
- Value Providers: You can also provide simple values using the
useValue
option.
@NgModule({
providers: [
{ provide: 'API_URL', useValue: 'https://api.example.com' }
],
})
export class AppModule {}
Then, inject this value into a service or component:
constructor(@Inject('API_URL') private apiUrl: string) {}
7. Lazy-Loaded Modules
- Services provided in a lazy-loaded module are only available to that module and its components. This is useful for optimizing performance and reducing the initial load time of the application.
Example Scenario: Creating and Injecting a Service
Let’s say you’re building a weather app and need a service to fetch weather data. ng generate service weather
- Create the Service:
// ng generate service weather
typescript
Copy code
// weather.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class WeatherService {
private apiUrl = 'https://api.weather.com/v3/wx/forecast';
constructor(private http: HttpClient) {}
getWeather(location: string) {
return this.http.get(`${this.apiUrl}?location=${location}`);
}
}
2. Inject the Service into a Component:
// weather.component.ts
import { Component, OnInit } from '@angular/core';
import { WeatherService } from './weather.service';
@Component({
selector: 'app-weather',
templateUrl: './weather.component.html',
styleUrls: ['./weather.component.css'],
})
export class WeatherComponent implements OnInit {
weatherData: any;
constructor(private weatherService: WeatherService) {} ngOnInit(): void {
this.weatherService.getWeather('New York').subscribe(data => {
this.weatherData = data;
});
}
}
3. Display the Data in the Template:
<!-- weather.component.html -->
<div *ngIf="weatherData">
<h1>Weather for {{ weatherData.location }}</h1>
<p>Temperature: {{ weatherData.temperature }}°C</p>
</div>
Angular’s DI system is powerful and flexible, allowing you to manage dependencies efficiently across your application. By understanding how to properly provide and inject services at different levels, you can create more modular, maintainable, and testable code.
Angular primarily uses Constructor Injection to handle dependency injection.
Constructor Injection in Angular
In Constructor Injection, dependencies are passed to a class via its constructor. Angular’s DI framework is designed around this approach, which means when you declare a dependency in the constructor of a class (such as a component or service), Angular’s injector will automatically resolve and inject that dependency when the class is instantiated.
- Automatic Resolution of Dependencies: Angular’s injector checks the metadata (like the
@Injectable
decorator) to understand how to resolve the dependencies. If the dependency is provided in the root module (providedIn: 'root'
), the injector will provide a singleton instance of the service. - Lazy Loading and Singleton Services: If the service is provided at a different level (e.g., a feature module), Angular may create a new instance for that module, allowing for lazy loading and optimization.
Why Angular Uses Constructor Injection:
- Immutability: Since dependencies are provided via the constructor, they can be assigned to readonly properties, ensuring they aren’t accidentally modified after being injected.
- Clarity: Constructor Injection makes it clear what dependencies a class needs, as they are all listed in one place — the constructor.
- Consistency: Constructor Injection aligns with Angular’s philosophy of declarative code, where the framework takes care of resolving dependencies based on declarative metadata.
Comparison with Other Injection Types:
- Setter Injection: Not commonly used in Angular because it introduces mutability, where dependencies can be changed after an object is created, which is not ideal for consistency and reliability.
- Interface Injection: This type is not used in Angular. Interface Injection involves dependencies being injected through an interface method, which is more common in languages like Java and isn’t natively supported in TypeScript/JavaScript.
Angular’s choice of Constructor Injection is driven by the desire for clear, maintainable, and predictable code. By ensuring that dependencies are injected at the time of object creation and are immutable, Angular promotes best practices in application development.
Want to more deep dive, other options of DI and uses in Angular, read here.
Thanks for reading.