Angular Dependency Injection
Configure and use dependency injection in Angular v20+ with inject() and providers.
Basic Injection
Using inject()
Prefer inject() over constructor injection:
typescript
1import { Component, inject } from '@angular/core';
2import { HttpClient } from '@angular/common/http';
3import { User } from './user.service';
4
5@Component({
6 selector: 'app-user-list',
7 template: `...`,
8})
9export class UserList {
10 // Inject dependencies
11 private http = inject(HttpClient);
12 private userService = inject(User);
13
14 // Can use immediately
15 users = this.userService.getUsers();
16}
Injectable Services
typescript
1import { Injectable, inject, signal } from '@angular/core';
2import { HttpClient } from '@angular/common/http';
3
4@Injectable({
5 providedIn: 'root', // Singleton at root level
6})
7export class User {
8 private http = inject(HttpClient);
9
10 private users = signal<User[]>([]);
11 readonly users$ = this.users.asReadonly();
12
13 async loadUsers() {
14 const users = await firstValueFrom(
15 this.http.get<User[]>('/api/users')
16 );
17 this.users.set(users);
18 }
19}
Provider Scopes
Root Level (Singleton)
typescript
1// Recommended: providedIn
2@Injectable({
3 providedIn: 'root',
4})
5export class Auth {}
6
7// Alternative: in app.config.ts
8export const appConfig: ApplicationConfig = {
9 providers: [
10 Auth,
11 ],
12};
Component Level (Instance per Component)
typescript
1@Component({
2 selector: 'app-editor',
3 providers: [EditorState], // New instance for each component
4 template: `...`,
5})
6export class Editor {
7 private editorState = inject(EditorState);
8}
Route Level
typescript
1export const routes: Routes = [
2 {
3 path: 'admin',
4 providers: [Admin], // Shared within this route tree
5 children: [
6 { path: '', component: AdminDashboard },
7 { path: 'users', component: AdminUsers },
8 ],
9 },
10];
Injection Tokens
Creating Tokens
typescript
1import { InjectionToken } from '@angular/core';
2
3// Simple value token
4export const API_URL = new InjectionToken<string>('API_URL');
5
6// Object token
7export interface AppConfig {
8 apiUrl: string;
9 features: {
10 darkMode: boolean;
11 analytics: boolean;
12 };
13}
14
15export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');
16
17// Token with factory (self-providing)
18export const WINDOW = new InjectionToken<Window>('Window', {
19 providedIn: 'root',
20 factory: () => window,
21});
22
23export const LOCAL_STORAGE = new InjectionToken<Storage>('LocalStorage', {
24 providedIn: 'root',
25 factory: () => localStorage,
26});
Providing Token Values
typescript
1// app.config.ts
2export const appConfig: ApplicationConfig = {
3 providers: [
4 { provide: API_URL, useValue: 'https://api.example.com' },
5 {
6 provide: APP_CONFIG,
7 useValue: {
8 apiUrl: 'https://api.example.com',
9 features: { darkMode: true, analytics: true },
10 },
11 },
12 ],
13};
Injecting Tokens
typescript
1@Injectable({ providedIn: 'root' })
2export class Api {
3 private apiUrl = inject(API_URL);
4 private config = inject(APP_CONFIG);
5 private window = inject(WINDOW);
6
7 getBaseUrl(): string {
8 return this.apiUrl;
9 }
10}
Provider Types
useClass
typescript
1// Provide implementation
2{ provide: Logger, useClass: ConsoleLogger }
3
4// Conditional implementation
5{
6 provide: Logger,
7 useClass: environment.production
8 ? ProductionLogger
9 : ConsoleLogger,
10}
useValue
typescript
1// Static values
2{ provide: API_URL, useValue: 'https://api.example.com' }
3
4// Configuration objects
5{ provide: APP_CONFIG, useValue: { theme: 'dark', language: 'en' } }
useFactory
typescript
1// Factory with dependencies
2{
3 provide: User,
4 useFactory: (http: HttpClient, config: AppConfig) => {
5 return new User(http, config.apiUrl);
6 },
7 deps: [HttpClient, APP_CONFIG],
8}
9
10// Async factory (not recommended - use provideAppInitializer)
11{
12 provide: CONFIG,
13 useFactory: () => fetch('/config.json').then(r => r.json()),
14}
useExisting
typescript
1// Alias to existing provider
2{ provide: AbstractLogger, useExisting: ConsoleLogger }
3
4// Multiple tokens pointing to same instance
5providers: [
6 ConsoleLogger,
7 { provide: Logger, useExisting: ConsoleLogger },
8 { provide: ErrorLogger, useExisting: ConsoleLogger },
9]
Injection Options
Optional Injection
typescript
1@Component({...})
2export class My {
3 // Returns null if not provided
4 private analytics = inject(Analytics, { optional: true });
5
6 trackEvent(name: string) {
7 this.analytics?.track(name);
8 }
9}
Self, SkipSelf, Host
typescript
1@Component({
2 providers: [Local],
3})
4export class Parent {
5 // Only look in this component's injector
6 private local = inject(Local, { self: true });
7}
8
9@Component({...})
10export class Child {
11 // Skip this component, look in parent
12 private parentService = inject(ParentSvc, { skipSelf: true });
13
14 // Only look up to host component
15 private hostService = inject(Host, { host: true });
16}
Multi Providers
Collect multiple values for same token:
typescript
1// Token for multiple validators
2export const VALIDATORS = new InjectionToken<Validator[]>('Validators');
3
4// Provide multiple values
5providers: [
6 { provide: VALIDATORS, useClass: RequiredValidator, multi: true },
7 { provide: VALIDATORS, useClass: EmailValidator, multi: true },
8 { provide: VALIDATORS, useClass: MinLengthValidator, multi: true },
9]
10
11// Inject as array
12@Injectable()
13export class Validation {
14 private validators = inject(VALIDATORS); // Validator[]
15
16 validate(value: string): ValidationError[] {
17 return this.validators
18 .map(v => v.validate(value))
19 .filter(Boolean);
20 }
21}
HTTP Interceptors (Multi Provider)
typescript
1// Interceptors use multi providers internally
2export const appConfig: ApplicationConfig = {
3 providers: [
4 provideHttpClient(
5 withInterceptors([
6 authInterceptor,
7 loggingInterceptor,
8 errorInterceptor,
9 ])
10 ),
11 ],
12};
App Initializers
Run async code before app starts using provideAppInitializer:
typescript
1import { provideAppInitializer, inject } from '@angular/core';
2
3export const appConfig: ApplicationConfig = {
4 providers: [
5 Config,
6 provideAppInitializer(() => {
7 const configService = inject(Config);
8 return configService.loadConfig();
9 }),
10 ],
11};
Multiple Initializers
typescript
1providers: [
2 provideAppInitializer(() => {
3 const config = inject(Config);
4 return config.load();
5 }),
6 provideAppInitializer(() => {
7 const auth = inject(Auth);
8 return auth.checkSession();
9 }),
10]
Environment Injector
Create injectors programmatically:
typescript
1import { createEnvironmentInjector, EnvironmentInjector, inject } from '@angular/core';
2
3@Injectable({ providedIn: 'root' })
4export class Plugin {
5 private parentInjector = inject(EnvironmentInjector);
6
7 loadPlugin(providers: Provider[]): EnvironmentInjector {
8 return createEnvironmentInjector(providers, this.parentInjector);
9 }
10}
runInInjectionContext
Run code with injection context:
typescript
1import { runInInjectionContext, EnvironmentInjector, inject } from '@angular/core';
2
3@Injectable({ providedIn: 'root' })
4export class Utility {
5 private injector = inject(EnvironmentInjector);
6
7 executeWithDI<T>(fn: () => T): T {
8 return runInInjectionContext(this.injector, fn);
9 }
10}
11
12// Usage
13utilityService.executeWithDI(() => {
14 const http = inject(HttpClient);
15 // Use http...
16});
For advanced patterns, see references/di-patterns.md.