Angular dependency injection resolves collaborators from hierarchical injectors. This lesson covers provider records, inject and constructor injection, InjectionToken, and the lifetime changes caused by root, route, and component scopes.
Dependency injection lets a class request collaborators instead of constructing them. Angular resolves each request from an active injector, which makes ownership, replacement in tests, and lifetime explicit.
Code requests a token; the injector finds a provider recipe, creates or returns the value, and caches it at that provider boundary. A class can be its own token, while InjectionToken represents configuration, functions, interfaces, and other values without a runtime class.
Angular starts with the nearest applicable element injector and then searches ancestor element and environment injectors. A nearer provider shadows the same token above it, which enables feature overrides but can accidentally split state that developers expected to share.
A route provider lives with that route environment. A component provider belongs to one component subtree, and each component instance receives a separate scoped service.
The inject function retrieves a token with accurate inferred types and works well in field initializers and provider factories. Constructor injection remains supported; choose one style consistently within a class.
inject works only in an injection context, including field initialization, construction, provider factories, and functions deliberately run in that context. Calling it later from an arbitrary event handler fails because no active injector is available.
Options support optional lookup and ancestor controls such as self, skipSelf, and host. Treat an optional result as nullable instead of asserting that a provider must exist.
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { CounterService } from './counter.service';
@Component({
selector: 'app-dashboard',
standalone: true,
template: `
<h2>Dashboard</h2>
<p>Count: {{ counter.count() }}</p>
<button (click)="navigate()">Go Home</button>
`
})
export class DashboardComponent {
// Modern inject() style - no constructor needed
private http = inject(HttpClient);
private router = inject(Router);
counter = inject(CounterService);
navigate() { this.router.navigate(['/home']); }
}
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { CounterService } from './counter.service';
@Component({
selector: 'app-dashboard',
standalone: true,
template: `<h2>Dashboard</h2>`
})
export class DashboardComponent {
// Traditional constructor injection
constructor(
private http: HttpClient,
private router: Router,
public counter: CounterService
) { }
}
A provider maps a token to a creation recipe. Use a class provider for a concrete implementation, a value provider for immutable configuration, a factory provider when construction depends on other tokens, and an existing provider when two tokens must resolve to the same instance.
Use InjectionToken for configuration values, interfaces, callbacks, and collections that do not exist as runtime classes. Give every token a descriptive name and a precise generic type so incorrect providers fail during compilation.
import { InjectionToken, inject } from '@angular/core';
export interface ApiConfig {
readonly baseUrl: string;
readonly timeoutMs: number;
}
export const API_CONFIG = new InjectionToken<ApiConfig>('API_CONFIG');
export const API_URL = new InjectionToken<string>('API_URL');
export const apiProviders = [
{
provide: API_CONFIG,
useValue: { baseUrl: '/api', timeoutMs: 5000 } satisfies ApiConfig
},
{
provide: API_URL,
useFactory: () => inject(API_CONFIG).baseUrl
}
];
abstract class AuditSink {
abstract record(event: string): void;
}
@Injectable()
class BrowserAuditSink implements AuditSink {
record(event: string): void {
console.info(event);
}
}
const auditProviders = [
BrowserAuditSink,
{ provide: AuditSink, useExisting: BrowserAuditSink }
];
// Both tokens resolve to the same BrowserAuditSink object.
Use providedIn: "root" for a tree-shakable service that should normally be shared. Use a component provider for disposable subtree state such as one editor session, and a route provider for state shared inside one lazy feature.
Do not provide a stateful service separately in siblings that must share one instance. Move the provider to their nearest common owner and verify that leaving the feature destroys the intended state.
import { Component, inject } from '@angular/core';
import { CounterService } from './counter.service';
@Component({
selector: 'app-root',
standalone: true,
// Each instance of this component gets its OWN CounterService
providers: [CounterService],
template: `
<p>Count: {{ counter.count() }}</p>
<button (click)="counter.increment()">+</button>
`
})
export class AppComponent {
counter = inject(CounterService);
}
Code that calls inject must run while Angular has an active injector. Field initializers, constructors, provider factories, router guards, and functions passed to runInInjectionContext are valid locations. A timer callback or ordinary utility function is not automatically an injection context.
Tests can replace a token without changing production code. Configure the real dependency only when the test needs its behavior; otherwise provide a focused fake and assert the class contract. TestBed.runInInjectionContext is useful for testing injectable functions such as guards.
TestBed.configureTestingModule({
providers: [
PriceService,
{ provide: API_CONFIG, useValue: { baseUrl: '/test-api', timeoutMs: 10 } }
]
});
const service = TestBed.inject(PriceService);
expect(service.endpoint).toBe('/test-api/prices');
A nearer provider is probably shadowing the root provider.
inject() needs an active Angular injection context.
Interfaces and configuration values have no runtime class for Angular to use as a provider key. An InjectionToken gives them one.
Explore 500+ free tutorials across 20+ languages and frameworks.