Constructor Injection Patterns
How constructor-based dependency injection works: private readonly, @Optional, @Self, InjectionToken, and multi providers
Overview
Constructor injection is Angular's traditional way to supply dependencies: the framework calls your constructor with the requested services. This practice covers how it works and common patterns: private readonly parameters, @Optional(), @Self(), @SkipSelf(), @Host(), InjectionToken, and multi providers. For the evolution to inject(), see the related practice.
The Evolution
Dependencies declared as constructor parameters; Angular injects them at creation time.
inject() offers an alternative; constructor injection remains fully supported.
Patterns
Basic constructor injection
@Component({ ... })export class DashboardComponent { constructor( private userService: UserService, private router: Router, private route: ActivatedRoute ) {}}@Component({ ... })export class DashboardComponent { private userService = inject(UserService); private router = inject(Router); private route = inject(ActivatedRoute);}private readonly pattern
constructor( private readonly userService: UserService, private readonly logger: LoggerService) {}// readonly prevents reassignment; private keeps it off templateprivate readonly userService = inject(UserService);private readonly logger = inject(LoggerService);Optional injection: @Optional()
constructor( private config: AppConfig, @Optional() private analytics: AnalyticsService) { if (this.analytics) { this.analytics.track('init'); }}private config = inject(AppConfig);private analytics = inject(AnalyticsService, { optional: true });
constructor() { if (this.analytics) { this.analytics.track('init'); }}InjectionToken and multi providers
const API_URL = new InjectionToken<string>('API_URL');
@Component({ ... })export class ApiComponent { constructor(@Inject(API_URL) private apiUrl: string) {}}const API_URL = new InjectionToken<string>('API_URL');
@Component({ ... })export class ApiComponent { private apiUrl = inject(API_URL);}Hierarchy: @Self, @SkipSelf, @Host
// @Self(): only look in the component's own injector// @SkipSelf(): skip component injector, use parentconstructor( @Self() private localConfig: ConfigService, @SkipSelf() private parentLogger: LoggerService) {}private localConfig = inject(ConfigService, { self: true });private parentLogger = inject(LoggerService, { skipSelf: true });Key Differences
Benefits
Dependencies listed in one place
Override in tests via TestBed
TypeScript types for tokens
@Optional(), multi providers
@Self, @SkipSelf, @Host for scope
When to use constructor injection
| Scenario | Pattern | |----------|---------| | Required service | constructor(private svc: MyService) or inject(MyService) | | Optional service | @Optional() private svc or inject(MyService, { optional: true }) | | InjectionToken | @Inject(TOKEN) or inject(TOKEN) | | Only self injector | @Self() or inject(..., { self: true }) | | Skip self (parent only) | @SkipSelf() or inject(..., { skipSelf: true }) | | Multi providers | inject(TOKEN) returns array when multi: true |
Prefer inject() for new code
Constructor injection is still valid. For new code, prefer inject() so you can use it in field initializers and outside the constructor (e.g. in guards, resolvers). See the related practice "From Constructor Injection to inject()".
Related Evolutions
Constructor Injection Patterns
How constructor-based dependency injection works: private readonly, @Optional, @Self, InjectionToken, and multi providers
Overview
Constructor injection is Angular's traditional way to supply dependencies: the framework calls your constructor with the requested services. This practice covers how it works and common patterns: private readonly parameters, @Optional(), @Self(), @SkipSelf(), @Host(), InjectionToken, and multi providers. For the evolution to inject(), see the related practice.
The Evolution
Dependencies declared as constructor parameters; Angular injects them at creation time.
inject() offers an alternative; constructor injection remains fully supported.
Patterns
Basic constructor injection
@Component({ ... })export class DashboardComponent { constructor( private userService: UserService, private router: Router, private route: ActivatedRoute ) {}}@Component({ ... })export class DashboardComponent { private userService = inject(UserService); private router = inject(Router); private route = inject(ActivatedRoute);}private readonly pattern
constructor( private readonly userService: UserService, private readonly logger: LoggerService) {}// readonly prevents reassignment; private keeps it off templateprivate readonly userService = inject(UserService);private readonly logger = inject(LoggerService);Optional injection: @Optional()
constructor( private config: AppConfig, @Optional() private analytics: AnalyticsService) { if (this.analytics) { this.analytics.track('init'); }}private config = inject(AppConfig);private analytics = inject(AnalyticsService, { optional: true });
constructor() { if (this.analytics) { this.analytics.track('init'); }}InjectionToken and multi providers
const API_URL = new InjectionToken<string>('API_URL');
@Component({ ... })export class ApiComponent { constructor(@Inject(API_URL) private apiUrl: string) {}}const API_URL = new InjectionToken<string>('API_URL');
@Component({ ... })export class ApiComponent { private apiUrl = inject(API_URL);}Hierarchy: @Self, @SkipSelf, @Host
// @Self(): only look in the component's own injector// @SkipSelf(): skip component injector, use parentconstructor( @Self() private localConfig: ConfigService, @SkipSelf() private parentLogger: LoggerService) {}private localConfig = inject(ConfigService, { self: true });private parentLogger = inject(LoggerService, { skipSelf: true });Key Differences
Benefits
Dependencies listed in one place
Override in tests via TestBed
TypeScript types for tokens
@Optional(), multi providers
@Self, @SkipSelf, @Host for scope
When to use constructor injection
| Scenario | Pattern | |----------|---------| | Required service | constructor(private svc: MyService) or inject(MyService) | | Optional service | @Optional() private svc or inject(MyService, { optional: true }) | | InjectionToken | @Inject(TOKEN) or inject(TOKEN) | | Only self injector | @Self() or inject(..., { self: true }) | | Skip self (parent only) | @SkipSelf() or inject(..., { skipSelf: true }) | | Multi providers | inject(TOKEN) returns array when multi: true |
Prefer inject() for new code
Constructor injection is still valid. For new code, prefer inject() so you can use it in field initializers and outside the constructor (e.g. in guards, resolvers). See the related practice "From Constructor Injection to inject()".