| name | angular-di |
| description | Implement dependency injection in Angular v20+ using inject(), injection tokens, and provider configuration. Use for service architecture, providing dependencies at different levels, creating injectable tokens, and managing singleton vs scoped services. Triggers on service creation, configuring providers, using injection tokens, or understanding DI hierarchy. |
Angular Dependency Injection
Configure and use dependency injection in Angular v20+ with inject() and providers.
Basic Injection
Using inject()
Prefer inject() over constructor injection:
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { UserService } from './user.service';
@Component({
selector: 'app-user-list',
template: `...`,
})
export class UserListComponent {
private http = inject(HttpClient);
private userService = inject(UserService);
users = this.userService.getUsers();
}
Injectable Services
import { Injectable, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class UserService {
private http = inject(HttpClient);
private users = signal<User[]>([]);
readonly users$ = this.users.asReadonly();
async loadUsers() {
const users = await firstValueFrom(
this.http.get<User[]>('/api/users')
);
this.users.set(users);
}
}
Provider Scopes
Root Level (Singleton)
@Injectable({
providedIn: 'root',
})
export class AuthService {}
export const appConfig: ApplicationConfig = {
providers: [
AuthService,
],
};
Component Level (Instance per Component)
@Component({
selector: 'app-editor',
providers: [EditorStateService],
template: `...`,
})
export class EditorComponent {
private editorState = inject(EditorStateService);
}
Route Level
export const routes: Routes = [
{
path: 'admin',
providers: [AdminService],
children: [
{ path: '', component: AdminDashboardComponent },
{ path: 'users', component: AdminUsersComponent },
],
},
];
Injection Tokens
Creating Tokens
import { InjectionToken } from '@angular/core';
export const API_URL = new InjectionToken<string>('API_URL');
export interface AppConfig {
apiUrl: string;
features: {
darkMode: boolean;
analytics: boolean;
};
}
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');
export const WINDOW = new InjectionToken<Window>('Window', {
providedIn: 'root',
factory: () => window,
});
export const LOCAL_STORAGE = new InjectionToken<Storage>('LocalStorage', {
providedIn: 'root',
: ,
});
Providing Token Values
export const appConfig: ApplicationConfig = {
providers: [
{ provide: API_URL, useValue: 'https://api.example.com' },
{
provide: APP_CONFIG,
useValue: {
apiUrl: 'https://api.example.com',
features: { darkMode: true, analytics: true },
},
},
],
};
Injecting Tokens
@Injectable({ providedIn: 'root' })
export class ApiService {
private apiUrl = inject(API_URL);
private config = inject(APP_CONFIG);
private window = inject(WINDOW);
getBaseUrl(): string {
return this.apiUrl;
}
}
Provider Types
useClass
{ provide: LoggerService, useClass: ConsoleLoggerService }
{
provide: LoggerService,
useClass: environment.production
? ProductionLoggerService
: ConsoleLoggerService,
}
useValue
{ provide: API_URL, useValue: 'https://api.example.com' }
{ provide: APP_CONFIG, useValue: { theme: 'dark', language: 'en' } }
useFactory
{
provide: UserService,
useFactory: (http: HttpClient, config: AppConfig) => {
return new UserService(http, config.apiUrl);
},
deps: [HttpClient, APP_CONFIG],
}
{
provide: CONFIG,
useFactory: () => fetch('/config.json').then(r => r.json()),
}
useExisting
{ provide: AbstractLogger, useExisting: ConsoleLoggerService }
providers: [
ConsoleLoggerService,
{ provide: Logger, useExisting: ConsoleLoggerService },
{ provide: ErrorLogger, useExisting: ConsoleLoggerService },
]
Injection Options
Optional Injection
@Component({...})
export class MyComponent {
private analytics = inject(AnalyticsService, { optional: true });
trackEvent(name: string) {
this.analytics?.track(name);
}
}
Self, SkipSelf, Host
@Component({
providers: [LocalService],
})
export class ParentComponent {
private local = inject(LocalService, { self: true });
}
@Component({...})
export class ChildComponent {
private parentService = inject(ParentService, { skipSelf: true });
private hostService = inject(HostService, { host: true });
}
Multi Providers
Collect multiple values for same token:
export const VALIDATORS = new InjectionToken<Validator[]>('Validators');
providers: [
{ provide: VALIDATORS, useClass: RequiredValidator, multi: true },
{ provide: VALIDATORS, useClass: EmailValidator, multi: true },
{ provide: VALIDATORS, useClass: MinLengthValidator, multi: true },
]
@Injectable()
export class ValidationService {
private validators = inject(VALIDATORS);
validate(value: string): ValidationError[] {
return this.validators
.map(v => v.validate(value))
.filter(Boolean);
}
}
HTTP Interceptors (Multi Provider)
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([
authInterceptor,
loggingInterceptor,
errorInterceptor,
])
),
],
};
App Initializers
Run async code before app starts using provideAppInitializer:
import { provideAppInitializer, inject } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
ConfigService,
provideAppInitializer(() => {
const configService = inject(ConfigService);
return configService.loadConfig();
}),
],
};
Multiple Initializers
providers: [
provideAppInitializer(() => {
const config = inject(ConfigService);
return config.load();
}),
provideAppInitializer(() => {
const auth = inject(AuthService);
return auth.checkSession();
}),
]
Environment Injector
Create injectors programmatically:
import { createEnvironmentInjector, EnvironmentInjector, inject } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class PluginService {
private parentInjector = inject(EnvironmentInjector);
loadPlugin(providers: Provider[]): EnvironmentInjector {
return createEnvironmentInjector(providers, this.parentInjector);
}
}
runInInjectionContext
Run code with injection context:
import { runInInjectionContext, EnvironmentInjector, inject } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class UtilityService {
private injector = inject(EnvironmentInjector);
executeWithDI<T>(fn: () => T): T {
return runInInjectionContext(this.injector, fn);
}
}
utilityService.executeWithDI(() => {
const http = inject(HttpClient);
});
For advanced patterns, see references/di-patterns.md.