Dependency Injection Overview in Angular
Angular’s Dependency Injection (DI) system provides a powerful way to manage and share services across your application. This guide covers the hierarchical nature of Angular’s injectors, how providers work at different levels, and best practices for structuring services to ensure modular, maintainable applications.
1. Introduction
Dependency Injection in Angular is a pattern where a class (such as a component or service) receives its dependencies from external sources rather than creating them itself. By default, Angular’s DI system is hierarchical, forming a tree of injectors that can provide different service instances based on the injector’s position in the hierarchy.
2. Hierarchical DI in Angular
At the root of every Angular application is the Root Injector, which typically provides services that are available application-wide. However, you can configure additional injectors at the module, component, or directive levels, allowing different parts of your application to have their own service instances.
- Root Injector: Created during the application bootstrap. Services provided here are generally singletons and can be accessed throughout the application.
- Module Injector: Each NgModule can declare its own providers. Services provided in feature modules can be scoped to that module (especially with lazy loading).
-
Component Injector: Specified via the
@Component
metadata’sproviders
array. Each component with aproviders
array creates a new injector for itself and its child components.
3. ProvidedIn Strategies
When creating a service, you can specify how and where it should be provided using the
@Injectable()
decorator’s providedIn
option. Common values:
-
providedIn: 'root'
– The service is provided in the root injector (singleton, app-wide). -
providedIn: 'any'
– A new instance is created in each lazy-loaded module. If the service is used in multiple modules, each gets its own instance. -
providedIn: 'platform'
– A special platform-level injector for multiple Angular apps running in the same document. -
Omitting
providedIn
and instead specifyingproviders: [MyService]
in a module or component is also valid, but less common with newer Angular best practices.
4. Example: Root vs. Component-Level Providers
Below is an example illustrating how a service provided at the root level differs from a service provided at the component level. Notice how each level creates its own instance of the service.
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MyService } from './my-service.service';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule],
providers: [MyService], // Root-level provider (alternative to providedIn: 'root')
bootstrap: [AppComponent]
})
export class AppModule { }
// my-service.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MyService {
private count = 0;
increment() { this.count++; }
getCount(): number { return this.count; }
}
// parent.component.ts
import { Component } from '@angular/core';
import { MyService } from './my-service.service';
@Component({
selector: 'app-parent',
template: ' ',
})
export class ParentComponent {
constructor(private myService: MyService) {
this.myService.increment();
}
}
// child.component.ts
import { Component } from '@angular/core';
import { MyService } from './my-service.service';
@Component({
selector: 'app-child',
template: 'Count: {{ myService.getCount() }}
',
providers: [MyService] // Component-level provider
})
export class ChildComponent {
constructor(public myService: MyService) {
this.myService.increment();
}
}
In this example:
- The root-level
MyService
is a singleton shared across most of the app. - The
MyService
declared inChildComponent
is a different instance, isolated to that component’s injector.
5. Best Practices
- Use ProvidedIn for Simplicity: Prefer
providedIn: 'root'
orprovidedIn: 'any'
in services. It’s simpler than manually listing providers in modules. - Singleton Services: If you want a single shared instance, provide the service in the root injector (or an eagerly loaded module).
- Component-Level Scoping: If a service is only needed by a specific component or subtree, declare it in that component’s
providers
. This reduces memory usage and prevents unintended sharing. - Lazy Loading: Use feature modules with
providedIn: 'any'
or module-level providers to create separate instances of a service for each lazy-loaded module. - Avoid Overusing Component Providers: While it can be useful for scoping, too many component-level services can complicate your architecture.
6. Further Examples: The forRoot() Pattern
Many libraries use a forRoot()
static method on their main module to configure and provide services.
This ensures a single instance of the service is created at the root level.
Feature modules might have a forChild()
method that doesn't provide the same services again,
preventing duplicates. For example:
// my-feature.module.ts
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MyService } from './my-service.service';
import { MyFeatureComponent } from './my-feature.component';
@NgModule({
imports: [CommonModule],
declarations: [MyFeatureComponent],
exports: [MyFeatureComponent]
})
export class MyFeatureModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: MyFeatureModule,
providers: [MyService]
};
}
}
This pattern helps manage whether a service is provided at the root injector or just in a child module.
It’s common in libraries like @ngrx/store
and @angular/router
.
7. Conclusion
Hierarchical Dependency Injection is a cornerstone of Angular’s design. It allows you to control
how and where services are instantiated, providing flexibility for both small-scale and complex enterprise apps.
By understanding the injector tree, using the right providedIn
strategy, and scoping services carefully,
you can create highly modular and efficient Angular applications.