Skip to main content
Practice Problems

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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems