What is Dependency Injection?
Dependency Injection (DI) is a design pattern where a class receives its dependencies from an external source rather than creating them itself. Angular has a built-in DI framework that is central to how the framework works.
Without DI (Tightly Coupled)
// ❌ Bad: Class creates its own dependency
class OrderService {
private http = new HttpClient(); // Hard to test, hard to swap
getOrders() {
return this.http.get('/api/orders');
}
}With DI (Loosely Coupled)
// ✅ Good: Dependency is injected
@Injectable({ providedIn: 'root' })
class OrderService {
private http = inject(HttpClient); // Angular provides it
getOrders() {
return this.http.get('/api/orders');
}
}How Angular's DI Works
Angular's DI system has three key concepts:
1. Provider → "Here's how to create this dependency"
2. Injector → "I manage dependencies and their lifecycle"
3. Consumer → "I need this dependency"The Flow
@Injectable({ providedIn: 'root' })
class MyService { } ← PROVIDER declares it
@Component(...)
class MyComponent {
service = inject(MyService); ← CONSUMER requests it
}
↕
INJECTOR resolves itProviding Services
Application-Wide (Most Common)
@Injectable({
providedIn: 'root', // Singleton for the entire app
})
export class AuthService { }In Application Config
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
// Custom providers
{ provide: API_URL, useValue: 'https://api.example.com' },
],
};In a Component (Scoped Instance)
@Component({
selector: 'app-editor',
standalone: true,
providers: [EditorStateService], // New instance for this component tree
template: `...`,
})
export class EditorComponent { }When provided in a component, each component instance gets its own service instance.
Injection Methods
inject() Function (Preferred)
import { inject } from '@angular/core';
@Component({ /* ... */ })
export class DashboardComponent {
private userService = inject(UserService);
private router = inject(Router);
private http = inject(HttpClient);
}Constructor Injection (Classic)
@Component({ /* ... */ })
export class DashboardComponent {
constructor(
private userService: UserService,
private router: Router,
private http: HttpClient,
) {}
}Provider Types
Angular supports several ways to define providers:
useClass — Provide a Class
providers: [
{ provide: LoggerService, useClass: LoggerService },
// Shorthand (equivalent):
LoggerService,
]Swap implementations:
providers: [
{ provide: LoggerService, useClass: ConsoleLoggerService },
// Whenever LoggerService is requested, provide ConsoleLoggerService
]useValue — Provide a Value
import { InjectionToken } from '@angular/core';
export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL');
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');
// In providers
providers: [
{ provide: API_BASE_URL, useValue: 'https://api.bigxstar.com' },
{ provide: APP_CONFIG, useValue: { debug: false, theme: 'dark' } },
]
// In a service
export class ApiService {
private baseUrl = inject(API_BASE_URL);
}useFactory — Provide via Factory Function
export const IS_BROWSER = new InjectionToken<boolean>('IS_BROWSER');
providers: [
{
provide: IS_BROWSER,
useFactory: () => typeof window !== 'undefined',
},
]Factory with dependencies:
providers: [
{
provide: UserService,
useFactory: (http: HttpClient, config: AppConfig) => {
return new UserService(http, config.apiUrl);
},
deps: [HttpClient, APP_CONFIG],
},
]useExisting — Alias a Provider
providers: [
ConsoleLoggerService,
{ provide: LoggerService, useExisting: ConsoleLoggerService },
// Both tokens resolve to the same ConsoleLoggerService instance
]Injection Tokens
When injecting non-class values (strings, numbers, objects, interfaces), use InjectionToken:
// tokens.ts
import { InjectionToken } from '@angular/core';
export interface AppSettings {
apiUrl: string;
debug: boolean;
maxRetries: number;
}
export const APP_SETTINGS = new InjectionToken<AppSettings>('APP_SETTINGS');
export const MAX_FILE_SIZE = new InjectionToken<number>('MAX_FILE_SIZE');// app.config.ts
providers: [
{
provide: APP_SETTINGS,
useValue: {
apiUrl: 'https://api.bigxstar.com',
debug: false,
maxRetries: 3,
},
},
{ provide: MAX_FILE_SIZE, useValue: 10 * 1024 * 1024 }, // 10MB
]// usage
export class UploadService {
private settings = inject(APP_SETTINGS);
private maxSize = inject(MAX_FILE_SIZE);
}Hierarchical Injector
Angular's DI system is hierarchical — there are multiple injector levels:
Root Injector (Application)
└── Component Injector (Parent)
└── Component Injector (Child)
└── Component Injector (Grandchild)When a component requests a dependency:
- Angular checks the component's own injector
- If not found, moves up to the parent component's injector
- Continues up until reaching the root injector
- If still not found, throws an error
Optional Dependencies
export class MyComponent {
// Returns null if not found (instead of throwing)
private analytics = inject(AnalyticsService, { optional: true });
trackEvent(name: string) {
this.analytics?.track(name);
}
}Self and SkipSelf
export class ChildComponent {
// Only look in THIS component's injector
private service = inject(MyService, { self: true });
// Skip THIS injector, start looking from parent
private parentService = inject(MyService, { skipSelf: true });
}Practical Example: Environment-Based Configuration
// environments/environment.ts
export const environment = {
production: false,
apiUrl: 'http://localhost:3000',
};
// injection token
export const ENVIRONMENT = new InjectionToken('ENVIRONMENT');
// app.config.ts
import { environment } from '../environments/environment';
export const appConfig: ApplicationConfig = {
providers: [
{ provide: ENVIRONMENT, useValue: environment },
provideHttpClient(),
provideRouter(routes),
],
};
// api.service.ts
@Injectable({ providedIn: 'root' })
export class ApiService {
private env = inject(ENVIRONMENT);
private http = inject(HttpClient);
get<T>(endpoint: string) {
return this.http.get<T>(`${this.env.apiUrl}${endpoint}`);
}
}Next Steps
With services and dependency injection mastered, you're ready to use one of Angular's most important built-in services — the HttpClient for making API calls and fetching data.