Dependency Injection (DI) is a design pattern and core feature of Angular that allows you to declare dependencies in your classes rather than creating instances yourself. Angular’s DI system handles the creation and lifecycle of service instances.
Why Dependency Injection?
Loose Coupling Components don’t need to know how to create dependencies
Testability Easy to provide mock dependencies for testing
Reusability Services can be shared across components
Maintainability Change implementations without modifying consumers
The @Injectable Decorator
Mark a class as available for injection:
import { Injectable } from '@angular/core' ;
@ Injectable ({
providedIn: 'root'
})
export class DataService {
private data : string [] = [];
getData () : string [] {
return this . data ;
}
addData ( item : string ) : void {
this . data . push ( item );
}
}
providedIn: 'root' makes the service available throughout the application as a singleton.
Provider Scopes
Angular supports several scopes for providing services:
Root Scope
Application-wide singleton:
import { Injectable } from '@angular/core' ;
@ Injectable ({
providedIn: 'root'
})
export class UserService {
private currentUser : User | null = null ;
login ( user : User ) : void {
this . currentUser = user ;
}
logout () : void {
this . currentUser = null ;
}
getCurrentUser () : User | null {
return this . currentUser ;
}
}
Component Scope
Provide a service at the component level:
import { Component } from '@angular/core' ;
import { LoggerService } from './logger.service' ;
@ Component ({
selector: 'app-user-profile' ,
standalone: true ,
providers: [ LoggerService ], // New instance for this component
template: `<div>{{ user.name }}</div>`
})
export class UserProfileComponent {
constructor ( private logger : LoggerService ) {
this . logger . log ( 'Component initialized' );
}
}
Shared across multiple Angular applications on the same page:
import { Injectable } from '@angular/core' ;
@ Injectable ({
providedIn: 'platform'
})
export class PlatformConfigService {
getConfig () {
return { apiUrl: 'https://api.example.com' };
}
}
Using inject() Function
The modern way to inject dependencies:
import { Component , inject } from '@angular/core' ;
import { HttpClient } from '@angular/common/http' ;
import { UserService } from './user.service' ;
@ Component ({
selector: 'app-dashboard' ,
standalone: true ,
template: `
<h1>Dashboard</h1>
<div>{{ user?.name }}</div>
`
})
export class DashboardComponent {
// Modern inject() function
private http = inject ( HttpClient );
private userService = inject ( UserService );
user = this . userService . getCurrentUser ();
ngOnInit () {
this . http . get ( '/api/data' ). subscribe ( data => {
console . log ( 'Data loaded:' , data );
});
}
}
inject() can only be called during the construction phase (in constructors, initializers, or factory functions).
Constructor Injection (Legacy)
Traditional approach using constructor parameters:
import { Component } from '@angular/core' ;
import { HttpClient } from '@angular/common/http' ;
import { UserService } from './user.service' ;
@ Component ({
selector: 'app-legacy' ,
standalone: true ,
template: `<div>Legacy component</div>`
})
export class LegacyComponent {
constructor (
private http : HttpClient ,
private userService : UserService
) {
// Dependencies are injected
}
}
Injection Tokens
Provide non-class dependencies:
import { InjectionToken , inject } from '@angular/core' ;
// Define a token
export const API_URL = new InjectionToken < string >( 'API URL' );
// Provide the token
import { ApplicationConfig } from '@angular/core' ;
export const appConfig : ApplicationConfig = {
providers: [
{ provide: API_URL , useValue: 'https://api.example.com' }
]
};
// Inject the token
import { Component } from '@angular/core' ;
@ Component ({
selector: 'app-api-consumer' ,
standalone: true ,
template: `<div>API: {{ apiUrl }}</div>`
})
export class ApiConsumerComponent {
apiUrl = inject ( API_URL );
}
Provider Types
Angular supports several provider configurations:
Class Provider
import { Injectable } from '@angular/core' ;
export abstract class Logger {
abstract log ( message : string ) : void ;
}
@ Injectable ()
export class ConsoleLogger implements Logger {
log ( message : string ) : void {
console . log ( message );
}
}
// Provide using useClass
import { ApplicationConfig } from '@angular/core' ;
export const appConfig : ApplicationConfig = {
providers: [
{ provide: Logger , useClass: ConsoleLogger }
]
};
Value Provider
interface AppConfig {
apiEndpoint : string ;
production : boolean ;
}
const CONFIG : AppConfig = {
apiEndpoint: 'https://api.example.com' ,
production: false
};
export const APP_CONFIG = new InjectionToken < AppConfig >( 'app.config' );
// Provide using useValue
export const appConfig : ApplicationConfig = {
providers: [
{ provide: APP_CONFIG , useValue: CONFIG }
]
};
Factory Provider
import { inject } from '@angular/core' ;
import { HttpClient } from '@angular/common/http' ;
export class ApiService {
constructor ( private http : HttpClient , private baseUrl : string ) {}
}
function apiServiceFactory () : ApiService {
const http = inject ( HttpClient );
const config = inject ( APP_CONFIG );
return new ApiService ( http , config . apiEndpoint );
}
// Provide using useFactory
export const appConfig : ApplicationConfig = {
providers: [
{ provide: ApiService , useFactory: apiServiceFactory }
]
};
Existing Provider
Alias one token to another:
@ Injectable ({ providedIn: 'root' })
export class NewService {
getData () { return [ 'new' , 'data' ]; }
}
export abstract class OldService {
abstract getData () : string [];
}
// Alias OldService to NewService
export const appConfig : ApplicationConfig = {
providers: [
NewService ,
{ provide: OldService , useExisting: NewService }
]
};
Optional Dependencies
Handle missing dependencies gracefully:
import { Component , inject , Optional } from '@angular/core' ;
import { LoggerService } from './logger.service' ;
@ Component ({
selector: 'app-flexible' ,
standalone: true ,
template: `<div>Flexible component</div>`
})
export class FlexibleComponent {
// Using inject() with optional
private logger = inject ( LoggerService , { optional: true });
doSomething () {
// Check if logger is available
this . logger ?. log ( 'Action performed' );
}
}
// Using constructor with @Optional
export class LegacyFlexibleComponent {
constructor (@ Optional () private logger ?: LoggerService ) {}
doSomething () {
this . logger ?. log ( 'Action performed' );
}
}
Self and SkipSelf
Control dependency resolution:
import { Component , inject , Self , SkipSelf } from '@angular/core' ;
import { DataService } from './data.service' ;
@ Component ({
selector: 'app-scoped' ,
standalone: true ,
providers: [ DataService ],
template: `<div>Scoped component</div>`
})
export class ScopedComponent {
// Inject only from this component's injector
private localService = inject ( DataService , { self: true });
// Skip this component's injector, use parent
private parentService = inject ( DataService , { skipSelf: true });
}
Hierarchical Injection
Angular creates a hierarchy of injectors:
Platform Injector
Shared across all applications on the page
Root Injector
Application-wide services (providedIn: ‘root’)
Module Injectors
Created for lazy-loaded modules (legacy)
Component Injectors
Created when components have providers
Element Injectors
Created for directives with providers
import { Component , Injectable } from '@angular/core' ;
@ Injectable ()
class CounterService {
count = 0 ;
increment () { this . count ++ ; }
}
@ Component ({
selector: 'parent-component' ,
standalone: true ,
providers: [ CounterService ], // Parent instance
template: `
<h2>Parent: {{ counter.count }}</h2>
<button (click)="counter.increment()">Increment</button>
<child-component />
`
})
export class ParentComponent {
counter = inject ( CounterService );
}
@ Component ({
selector: 'child-component' ,
standalone: true ,
// No providers - inherits from parent
template: `
<h3>Child: {{ counter.count }}</h3>
<button (click)="counter.increment()">Increment</button>
`
})
export class ChildComponent {
counter = inject ( CounterService ); // Same instance as parent
}
Best Practices
Use inject() over constructor injection for modern applications
Provide in root for application-wide singletons
Use component providers for component-specific state
Leverage injection tokens for configuration and non-class dependencies
Keep services focused on single responsibilities
Avoid circular dependencies between services
Use factory providers for complex initialization logic
Next Steps
Services Learn to create and use services
Components Understand component architecture