--- name: angular-dependency-injection user-invocable: false description: Use when building modular Angular applications requiring dependency injection with providers, injectors, and services. allowed-tools: - Bash - Read --- # Angular Dependency Injection Master Angular's dependency injection system for building modular, testable applications with proper service architecture. ## DI Fundamentals Angular's DI uses the `inject()` function (Angular 14+) as the preferred injection mechanism — no constructor parameters needed: ```typescript import { Injectable, inject } from '@angular/core'; // Service injectable at root level @Injectable({ providedIn: 'root' }) export class UserService { private users: User[] = []; getUsers(): User[] { return this.users; } addUser(user: User): void { this.users.push(user); } } // Standalone component injection via inject() import { Component } from '@angular/core'; @Component({ selector: 'app-user-list', standalone: true, template: ` @for (user of users; track user.id) {
{{ user.name }}
} ` }) export class UserListComponent { private readonly userService = inject(UserService); users = this.userService.getUsers(); } ``` ## Provider Types ### useClass - Class Provider ```typescript import { Injectable, Provider, Component, inject } from '@angular/core'; // Interface interface Logger { log(message: string): void; } // Implementations @Injectable() export class ConsoleLogger implements Logger { log(message: string): void { console.log(message); } } @Injectable() export class FileLogger implements Logger { log(message: string): void { // Write to file } } // Provider configuration const loggerProvider: Provider = { provide: Logger, useClass: ConsoleLogger }; // Standalone component — providers go here, not in NgModule @Component({ selector: 'app-my-component', standalone: true, providers: [loggerProvider], template: `...` }) export class MyComponent { private readonly logger = inject(Logger); constructor() { this.logger.log('Component initialized'); } } ``` ### useValue - Value Provider ```typescript import { InjectionToken, Component, inject } from '@angular/core'; // Configuration object export interface AppConfig { apiUrl: string; timeout: number; retries: number; } export const APP_CONFIG = new InjectionToken('app.config'); // Provider const configProvider = { provide: APP_CONFIG, useValue: { apiUrl: 'https://api.example.com', timeout: 5000, retries: 3 } }; // bootstrapApplication (standalone) bootstrapApplication(AppComponent, { providers: [configProvider] }); // Usage via inject() export class ApiService { private readonly config = inject(APP_CONFIG); constructor() { console.log(this.config.apiUrl); } } ``` ### useFactory - Factory Provider ```typescript import { Injectable, InjectionToken, inject } from '@angular/core'; export const API_URL = new InjectionToken('api.url'); // Use inject() inside the factory — no deps array needed const apiUrlProvider = { provide: API_URL, useFactory: () => { const config = inject(AppConfig); return config.production ? 'https://api.prod.example.com' : 'https://api.dev.example.com'; } }; // Complex factory — all deps resolved via inject() const httpClientProvider = { provide: HttpClient, useFactory: () => { const handler = inject(HttpHandler); const logger = inject(Logger); logger.log('Creating HTTP client'); return new HttpClient(handler); } }; ``` ### useExisting - Alias Provider ```typescript import { Injectable, inject } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class NewLogger { log(message: string): void { console.log('[NEW]', message); } } // Alias an abstract token to the concrete implementation export abstract class Logger { abstract log(message: string): void; } const loggerAlias: Provider = { provide: Logger, useExisting: NewLogger }; // Usage export class MyComponent { private readonly logger = inject(Logger); constructor() { this.logger.log('Using aliased logger'); } } ``` ## Injection Tokens ### InjectionToken - Type-Safe Tokens ```typescript import { InjectionToken, inject } from '@angular/core'; // Primitive token export const MAX_RETRIES = new InjectionToken('max.retries', { providedIn: 'root', factory: () => 3 // Default value }); // Object token export interface FeatureFlags { enableNewUI: boolean; enableBeta: boolean; } export const FEATURE_FLAGS = new InjectionToken( 'feature.flags', { providedIn: 'root', factory: () => ({ enableNewUI: false, enableBeta: false }) } ); // Usage via inject() @Injectable() export class ApiService { private readonly maxRetries = inject(MAX_RETRIES); private readonly flags = inject(FEATURE_FLAGS); } ``` ## Hierarchical Injectors ### Root Injector ```typescript // Singleton across entire app @Injectable({ providedIn: 'root' }) export class GlobalService { private state = {}; } ``` ### Component Injector ```typescript @Injectable() export class ComponentService { private data = []; } @Component({ selector: 'app-my-component', standalone: true, template: '...', providers: [ComponentService] // New instance per component }) export class MyComponent { private readonly service = inject(ComponentService); } // Each component instance gets its own service instance ``` ### Element Injector ```typescript @Directive({ selector: '[appHighlight]', standalone: true, providers: [DirectiveService] }) export class HighlightDirective { private readonly service = inject(DirectiveService); } ``` ## ProvidedIn Options ```typescript // Root - singleton across entire app @Injectable({ providedIn: 'root' }) export class RootService {} // Platform - shared across multiple apps @Injectable({ providedIn: 'platform' }) export class PlatformService {} ``` ## Optional and Scoped Injection Use `inject()` options instead of `@Optional`, `@Self`, `@SkipSelf`, `@Host`: ```typescript import { inject } from '@angular/core'; export class MyService { // @Optional() equivalent private readonly logger = inject(Logger, { optional: true }); // @Self() equivalent — only from this component's injector private readonly local = inject(LocalService, { self: true }); // @SkipSelf() equivalent — skip this injector, look up the tree private readonly parent = inject(SharedService, { skipSelf: true }); // @Host() equivalent — stop at host element private readonly host = inject(ParentComponent, { host: true }); // Combine options readonly #dep = inject(Dep, { optional: true, self: true, }); constructor() { this.logger?.log('Service created'); } } ``` ## Environment Providers (replaces ForRoot/ForChild) Use `makeEnvironmentProviders` and `provideX()` functions instead of `NgModule.forRoot()` / `NgModule.forChild()`: ```typescript import { makeEnvironmentProviders, EnvironmentProviders } from '@angular/core'; export interface SharedConfig { apiUrl: string; } export const SHARED_CONFIG = new InjectionToken('shared.config'); // Replace forRoot() with a provideX() function export function provideShared(config: SharedConfig): EnvironmentProviders { return makeEnvironmentProviders([ SharedService, { provide: SHARED_CONFIG, useValue: config } ]); } // In bootstrapApplication (app root) bootstrapApplication(AppComponent, { providers: [ provideShared({ apiUrl: 'https://api.example.com' }), provideRouter(routes), provideHttpClient() ] }); // In lazy-loaded routes — child-scope providers export const routes: Routes = [ { path: 'feature', loadComponent: () => import('./feature.component'), providers: [FeatureScopedService] } ]; ``` ## Multi-Providers ```typescript import { InjectionToken, inject } from '@angular/core'; export const HTTP_INTERCEPTORS = new InjectionToken('http.interceptors'); @Injectable() export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler) { return next.handle(req); } } @Injectable() export class LoggingInterceptor implements HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler) { return next.handle(req); } } // Register as multi-providers const providers: Provider[] = [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true } ]; // Inject as array export class HttpService { private readonly interceptors = inject(HTTP_INTERCEPTORS); } ``` ## Tree-Shakable Providers ```typescript // Tree-shakable (preferred) — service is removed if never injected @Injectable({ providedIn: 'root' }) export class NewService {} ``` ## Testing with DI ### TestBed Provider Overrides ```typescript import { TestBed } from '@angular/core/testing'; describe('MyComponent', () => { let mockUserService: jasmine.SpyObj; beforeEach(() => { mockUserService = jasmine.createSpyObj('UserService', ['getUsers']); TestBed.configureTestingModule({ imports: [MyComponent], // standalone component providers: [ { provide: UserService, useValue: mockUserService } ] }); }); it('should get users', () => { mockUserService.getUsers.and.returnValue([]); const fixture = TestBed.createComponent(MyComponent); // Test component with mock }); }); ``` ### Spy on Dependencies ```typescript import { TestBed } from '@angular/core/testing'; describe('UserService', () => { let service: UserService; let httpMock: jasmine.SpyObj; beforeEach(() => { httpMock = jasmine.createSpyObj('HttpClient', ['get', 'post']); TestBed.configureTestingModule({ providers: [ UserService, { provide: HttpClient, useValue: httpMock } ] }); service = TestBed.inject(UserService); }); it('should fetch users', () => { httpMock.get.and.returnValue(of([])); service.getUsers().subscribe(); expect(httpMock.get).toHaveBeenCalled(); }); }); ``` ## When to Use This Skill Use angular-dependency-injection when building modern, production-ready applications that require: - Modular service architecture - Testable components and services - Configuration management - Plugin/extension systems - Multi-provider patterns (interceptors, validators) - Complex service hierarchies - Lazy-loaded route isolation - Tree-shakable code ## Angular DI Best Practices 1. **Use `inject()`** — Cleaner than constructor injection, works in class fields 2. **Use `providedIn: 'root'`** — Tree-shakable and singleton 3. **Use InjectionToken** — Type-safe tokens, no string tokens 4. **Use standalone components** — Providers in `@Component`, not `@NgModule` 5. **Use `provideX()` functions** — Replaces `forRoot()`/`forChild()` 6. **Use `inject()` in factories** — No `deps` array needed 7. **Use `inject()` options** — Replaces `@Optional`, `@Self`, `@SkipSelf`, `@Host` 8. **Favor composition** — Inject small, focused services 9. **Test with mocks** — Override providers in TestBed 10. **Avoid circular dependencies** — Refactor to a common service ## Resources - [Angular Dependency Injection Guide](https://angular.dev/guide/di) - [inject() function](https://angular.dev/api/core/inject) - [Environment Injectors](https://angular.dev/guide/di/environment-injectors) - [Injectable Services](https://angular.dev/guide/di/creating-injectable-service) - [Testing with DI](https://angular.dev/guide/testing/services)