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:
typescriptimport { 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) { <div>{{ user.name }}</div> } ` }) export class UserListComponent { private readonly userService = inject(UserService); users = this.userService.getUsers(); }
Provider Types
useClass - Class Provider
typescriptimport { 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
typescriptimport { InjectionToken, Component, inject } from '@angular/core'; // Configuration object export interface AppConfig { apiUrl: string; timeout: number; retries: number; } export const APP_CONFIG = new InjectionToken<AppConfig>('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
typescriptimport { Injectable, InjectionToken, inject } from '@angular/core'; export const API_URL = new InjectionToken<string>('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
typescriptimport { 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
typescriptimport { InjectionToken, inject } from '@angular/core'; // Primitive token export const MAX_RETRIES = new InjectionToken<number>('max.retries', { providedIn: 'root', factory: () => 3 // Default value }); // Object token export interface FeatureFlags { enableNewUI: boolean; enableBeta: boolean; } export const FEATURE_FLAGS = new InjectionToken<FeatureFlags>( '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:
typescriptimport { 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():
typescriptimport { makeEnvironmentProviders, EnvironmentProviders } from '@angular/core'; export interface SharedConfig { apiUrl: string; } export const SHARED_CONFIG = new InjectionToken<SharedConfig>('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
typescriptimport { InjectionToken, inject } from '@angular/core'; export const HTTP_INTERCEPTORS = new InjectionToken<HttpInterceptor[]>('http.interceptors'); @Injectable() export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler) { return next.handle(req); } } @Injectable() export class LoggingInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, 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
typescriptimport { TestBed } from '@angular/core/testing'; describe('MyComponent', () => { let mockUserService: jasmine.SpyObj<UserService>; 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
typescriptimport { TestBed } from '@angular/core/testing'; describe('UserService', () => { let service: UserService; let httpMock: jasmine.SpyObj<HttpClient>; 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
- Use
inject()— Cleaner than constructor injection, works in class fields - Use
providedIn: 'root'— Tree-shakable and singleton - Use InjectionToken — Type-safe tokens, no string tokens
- Use standalone components — Providers in
@Component, not@NgModule - Use
provideX()functions — ReplacesforRoot()/forChild() - Use
inject()in factories — Nodepsarray needed - Use
inject()options — Replaces@Optional,@Self,@SkipSelf,@Host - Favor composition — Inject small, focused services
- Test with mocks — Override providers in TestBed
- Avoid circular dependencies — Refactor to a common service

