inject() Function Usage
How to use the inject() function for dependency injection: components, services, guards, resolvers, and injection context
Overview
The inject() function provides a modern alternative to constructor-based dependency injection. It offers more flexibility, works with functional patterns, and enables powerful composition through custom inject functions.
The Evolution
The traditional way to inject dependencies via constructor parameters.
New function-based injection introduced.
inject() can be used in class field initializers.
Code Comparison
Basic Service Injection
import { Component } from '@angular/core';import { UserService } from './user.service';import { AuthService } from './auth.service';import { LoggerService } from './logger.service';
@Component({selector: 'app-dashboard',template: `...`})export class DashboardComponent {constructor(private userService: UserService,private authService: AuthService,private logger: LoggerService) {}
ngOnInit() {this.logger.log('Dashboard initialized');this.loadUser();}
private loadUser() {if (this.authService.isAuthenticated()) {this.userService.getCurrentUser();}}}import { Component, inject } from '@angular/core';import { UserService } from './user.service';import { AuthService } from './auth.service';import { LoggerService } from './logger.service';
@Component({ selector: 'app-dashboard', template: `...`})export class DashboardComponent { private userService = inject(UserService); private authService = inject(AuthService); private logger = inject(LoggerService);
ngOnInit() { this.logger.log('Dashboard initialized'); this.loadUser(); }
private loadUser() { if (this.authService.isAuthenticated()) { this.userService.getCurrentUser(); } }}Optional Dependencies
import { Component, Optional, Inject } from '@angular/core';import { CONFIG_TOKEN, AppConfig } from './config';
@Component({selector: 'app-settings',template: `...`})export class SettingsComponent {constructor(@Optional()@Inject(CONFIG_TOKEN)private config: AppConfig | null) {if (this.config) {console.log('Config loaded:', this.config);}}}import { Component, inject } from '@angular/core';import { CONFIG_TOKEN, AppConfig } from './config';
@Component({ selector: 'app-settings', template: `...`})export class SettingsComponent { private config = inject(CONFIG_TOKEN, { optional: true });
constructor() { if (this.config) { console.log('Config loaded:', this.config); } }}Custom Inject Functions
export abstract class BaseFormComponent { protected fb: FormBuilder; protected router: Router; protected snackbar: MatSnackBar;
constructor(injector: Injector) {this.fb = injector.get(FormBuilder);this.router = injector.get(Router);this.snackbar = injector.get(MatSnackBar);}
protected showSuccess(message: string) {this.snackbar.open(message, 'OK');}}
// user-form.component.ts@Component({ ... })export class UserFormComponent extends BaseFormComponent {constructor(injector: Injector) {super(injector);}}export function injectFormDeps() { return { fb: inject(FormBuilder), router: inject(Router), snackbar: inject(MatSnackBar), showSuccess: (message: string) => { inject(MatSnackBar).open(message, 'OK'); } };}
// user-form.component.ts@Component({ ... })export class UserFormComponent { private formDeps = injectFormDeps();
form = this.formDeps.fb.group({ name: ['', Validators.required] });
onSubmit() { // Save logic... this.formDeps.showSuccess('User saved!'); this.formDeps.router.navigate(['/users']); }}Inject in Services
import { Injectable } from '@angular/core';import { HttpClient } from '@angular/common/http';import { Router } from '@angular/router';
@Injectable({ providedIn: 'root' })export class ApiService {constructor(private http: HttpClient,private router: Router) {}
get<T>(url: string) {return this.http.get<T>(url);}
handleUnauthorized() {this.router.navigate(['/login']);}}import { Injectable, inject } from '@angular/core';import { HttpClient } from '@angular/common/http';import { Router } from '@angular/router';
@Injectable({ providedIn: 'root' })export class ApiService { private http = inject(HttpClient); private router = inject(Router);
get<T>(url: string) { return this.http.get<T>(url); }
handleUnauthorized() { this.router.navigate(['/login']); }}Key Differences
Benefits
Less boilerplate, no super() forwarding
Composable inject functions, cleaner inheritance
Easier to mock, works with functional APIs
Optional via inject options
Familiar pattern for functional code
Injection Context
The inject() function must be called in an injection context: - During class
construction (constructor or field initializers) - Inside factory functions
for providers - Inside runInInjectionContext() for dynamic injection
Creating Reusable Inject Functions
One of the most powerful patterns with inject() is creating composable utilities:
export function injectDestroy() { const destroyRef = inject(DestroyRef); const subject = new Subject<void>();
destroyRef.onDestroy(() => { subject.next(); subject.complete(); });
return subject.asObservable();}
// Usage in component@Component({ ... })export class MyComponent { private destroy$ = injectDestroy();
ngOnInit() { interval(1000).pipe( takeUntil(this.destroy$) ).subscribe(console.log); }}Injection Context Required
Calling inject() outside an injection context will throw an error. If you
need to inject dependencies dynamically, use runInInjectionContext() from
the Injector.
Benefits of inject()
- Less Boilerplate: No need to repeat parameters in super() calls
- Better Composition: Create reusable inject functions
- Cleaner Inheritance: No constructor parameter forwarding
- Works with Functional APIs: Guards, resolvers, interceptors
- Tree Shakeable: Better for removing unused services
Related Evolutions
inject() Function Usage
How to use the inject() function for dependency injection: components, services, guards, resolvers, and injection context
Overview
The inject() function provides a modern alternative to constructor-based dependency injection. It offers more flexibility, works with functional patterns, and enables powerful composition through custom inject functions.
The Evolution
The traditional way to inject dependencies via constructor parameters.
New function-based injection introduced.
inject() can be used in class field initializers.
Code Comparison
Basic Service Injection
import { Component } from '@angular/core';import { UserService } from './user.service';import { AuthService } from './auth.service';import { LoggerService } from './logger.service';
@Component({selector: 'app-dashboard',template: `...`})export class DashboardComponent {constructor(private userService: UserService,private authService: AuthService,private logger: LoggerService) {}
ngOnInit() {this.logger.log('Dashboard initialized');this.loadUser();}
private loadUser() {if (this.authService.isAuthenticated()) {this.userService.getCurrentUser();}}}import { Component, inject } from '@angular/core';import { UserService } from './user.service';import { AuthService } from './auth.service';import { LoggerService } from './logger.service';
@Component({ selector: 'app-dashboard', template: `...`})export class DashboardComponent { private userService = inject(UserService); private authService = inject(AuthService); private logger = inject(LoggerService);
ngOnInit() { this.logger.log('Dashboard initialized'); this.loadUser(); }
private loadUser() { if (this.authService.isAuthenticated()) { this.userService.getCurrentUser(); } }}Optional Dependencies
import { Component, Optional, Inject } from '@angular/core';import { CONFIG_TOKEN, AppConfig } from './config';
@Component({selector: 'app-settings',template: `...`})export class SettingsComponent {constructor(@Optional()@Inject(CONFIG_TOKEN)private config: AppConfig | null) {if (this.config) {console.log('Config loaded:', this.config);}}}import { Component, inject } from '@angular/core';import { CONFIG_TOKEN, AppConfig } from './config';
@Component({ selector: 'app-settings', template: `...`})export class SettingsComponent { private config = inject(CONFIG_TOKEN, { optional: true });
constructor() { if (this.config) { console.log('Config loaded:', this.config); } }}Custom Inject Functions
export abstract class BaseFormComponent { protected fb: FormBuilder; protected router: Router; protected snackbar: MatSnackBar;
constructor(injector: Injector) {this.fb = injector.get(FormBuilder);this.router = injector.get(Router);this.snackbar = injector.get(MatSnackBar);}
protected showSuccess(message: string) {this.snackbar.open(message, 'OK');}}
// user-form.component.ts@Component({ ... })export class UserFormComponent extends BaseFormComponent {constructor(injector: Injector) {super(injector);}}export function injectFormDeps() { return { fb: inject(FormBuilder), router: inject(Router), snackbar: inject(MatSnackBar), showSuccess: (message: string) => { inject(MatSnackBar).open(message, 'OK'); } };}
// user-form.component.ts@Component({ ... })export class UserFormComponent { private formDeps = injectFormDeps();
form = this.formDeps.fb.group({ name: ['', Validators.required] });
onSubmit() { // Save logic... this.formDeps.showSuccess('User saved!'); this.formDeps.router.navigate(['/users']); }}Inject in Services
import { Injectable } from '@angular/core';import { HttpClient } from '@angular/common/http';import { Router } from '@angular/router';
@Injectable({ providedIn: 'root' })export class ApiService {constructor(private http: HttpClient,private router: Router) {}
get<T>(url: string) {return this.http.get<T>(url);}
handleUnauthorized() {this.router.navigate(['/login']);}}import { Injectable, inject } from '@angular/core';import { HttpClient } from '@angular/common/http';import { Router } from '@angular/router';
@Injectable({ providedIn: 'root' })export class ApiService { private http = inject(HttpClient); private router = inject(Router);
get<T>(url: string) { return this.http.get<T>(url); }
handleUnauthorized() { this.router.navigate(['/login']); }}Key Differences
Benefits
Less boilerplate, no super() forwarding
Composable inject functions, cleaner inheritance
Easier to mock, works with functional APIs
Optional via inject options
Familiar pattern for functional code
Injection Context
The inject() function must be called in an injection context: - During class
construction (constructor or field initializers) - Inside factory functions
for providers - Inside runInInjectionContext() for dynamic injection
Creating Reusable Inject Functions
One of the most powerful patterns with inject() is creating composable utilities:
export function injectDestroy() { const destroyRef = inject(DestroyRef); const subject = new Subject<void>();
destroyRef.onDestroy(() => { subject.next(); subject.complete(); });
return subject.asObservable();}
// Usage in component@Component({ ... })export class MyComponent { private destroy$ = injectDestroy();
ngOnInit() { interval(1000).pipe( takeUntil(this.destroy$) ).subscribe(console.log); }}Injection Context Required
Calling inject() outside an injection context will throw an error. If you
need to inject dependencies dynamically, use runInInjectionContext() from
the Injector.
Benefits of inject()
- Less Boilerplate: No need to repeat parameters in super() calls
- Better Composition: Create reusable inject functions
- Cleaner Inheritance: No constructor parameter forwarding
- Works with Functional APIs: Guards, resolvers, interceptors
- Tree Shakeable: Better for removing unused services