angular.courses

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

v2Constructor Injection

The traditional way to inject dependencies via constructor parameters.

v14inject() Function

New function-based injection introduced.

v15Field Initializers

inject() can be used in class field initializers.

Code Comparison

Basic Service Injection

Constructor InjectionBefore v13
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();
}
}
}
inject() Functionv14+
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

@Optional() DecoratorBefore v13
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);
}
}
}
inject() with optionsv14+
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

Base Class PatternBefore v13
base-form.component.ts
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);
}
}
Composable inject()v14+
form.utils.ts
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

Constructor in ServiceBefore v13
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']);
}
}
inject() in Servicev14+
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

Syntax:constructor parametersinject() calls
Decorators:@Optional(), @Inject()inject() options
Composition:Base classesCustom inject functions
Location:Constructor onlyField initializers

Benefits

Developer Experience

Less boilerplate, no super() forwarding

Maintainability

Composable inject functions, cleaner inheritance

Testability

Easier to mock, works with functional APIs

Type Safety

Optional via inject options

Learning Curve

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:

destroy.util.ts
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()

  1. Less Boilerplate: No need to repeat parameters in super() calls
  2. Better Composition: Create reusable inject functions
  3. Cleaner Inheritance: No constructor parameter forwarding
  4. Works with Functional APIs: Guards, resolvers, interceptors
  5. Tree Shakeable: Better for removing unused services