Advanced dependency injection patterns in Angular
Advanced DI Patterns
Angular's Dependency Injection system goes beyond basic service injection. Understanding advanced patterns is crucial for building scalable applications.
Multi Providers
Register multiple values for the same token:
typescript
const HTTP_INTERCEPTORS = new InjectionToken<HttpInterceptor[]>('interceptors');
// Each provider ADDS to the array
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
]
// Injecting gets ALL of them
constructor(@Inject(HTTP_INTERCEPTORS) private interceptors: HttpInterceptor[]) {
// interceptors = [AuthInterceptor, LoggingInterceptor, ErrorInterceptor]
}Factory Providers
Create dependencies with dynamic logic:
typescript
{ provide: LoggerService, useFactory: () => {
const env = inject(EnvironmentService);
return env.isProduction
? new ProductionLogger()
: new ConsoleLogger();
}
}Optional and Self/SkipSelf
typescript
@Component({ /* ... */ })
export class MyComponent {
// Won't throw if not found
constructor(@Optional() private analytics?: AnalyticsService) {}
// Only look in THIS component's injector
constructor(@Self() private service: MyService) {}
// Skip THIS injector, look in parent
constructor(@SkipSelf() private service: MyService) {}
}Tree-Shakeable Providers
typescript
// ✅ Tree-shakeable — removed if not injected anywhere
@Injectable({ providedIn: 'root' })
export class UserService {}
// ❌ Not tree-shakeable — always included
@NgModule({ providers: [UserService] })
export class UserModule {}Component-Level Providers
typescript
// Each component instance gets its OWN service instance
@Component({
providers: [FormStateService] // Not shared with other components
})
export class FormComponent {
constructor(private formState: FormStateService) {}
// Each FormComponent has its own FormStateService
}Abstract Class as Token
typescript
// Define abstract contract
abstract class Storage {
abstract get(key: string): string | null;
abstract set(key: string, value: string): void;
}
// Implementation
class LocalStorageService extends Storage {
get(key: string) { return localStorage.getItem(key); }
set(key: string, value: string) { localStorage.setItem(key, value); }
}
// Provide implementation
{ provide: Storage, useClass: LocalStorageService }
// Inject the abstraction
constructor(private storage: Storage) {
this.storage.get('token'); // Uses LocalStorageService
}Environment-Based Config
typescript
export const APP_CONFIG = new InjectionToken<AppConfig>('app.config');
// In main.ts
bootstrapApplication(AppComponent, {
providers: [
{
provide: APP_CONFIG,
useValue: {
apiUrl: environment.apiUrl,
debug: !environment.production,
features: environment.features,
}
}
]
});
// In any service/component
private config = inject(APP_CONFIG);Important:
Advanced DI patterns include multi providers (plugin systems, interceptors), factory providers (conditional logic), and abstract class tokens (clean interfaces). Use providedIn: 'root' for tree-shakeable singletons. Use component-level providers for isolated instances. Understanding DI hierarchy (Self, SkipSelf, Optional) is key for complex applications.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.