Angular dependency injection (DI) is the framework mechanism that creates and supplies dependencies (services/config/values) to classes (components, directives, pipes, other services) based on providers. Instead of constructing dependencies manually, you register providers (how to build a value for a token) and Angular resolves them through a hierarchical injector tree. Interview focus: provider configuration, token types (class vs InjectionToken), scoping/lifetime (root vs component), and common pitfalls (multiple instances, circular deps, Optional/Self/SkipSelf). DI affects testability and architecture; test with mocks and scope providers carefully.
What is dependency injection in Angular?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core idea
DI is how Angular finds and creates what your class needs. You request a dependency by token (usually a class), and Angular returns an instance/value based on the closest matching provider in the injector hierarchy.
Term | What it is | What interviewers expect you to say |
|---|---|---|
Token | The lookup key for a dependency (class type or | “Angular resolves dependencies by token; classes are tokens, but for primitives/objects you use |
Provider | A rule that tells Angular how to produce a value for a token ( | “Providers map token → value/instance creation strategy.” |
Injector | A container that holds providers and can resolve tokens. Injectors form a tree. | “Resolution walks up the injector tree; the closest provider wins.” |
Scope / lifetime | Where the provider is registered determines whether you get one shared instance or many. | “Root providers are app-singletons; component providers create per-component instances.” |
How resolution works (hierarchical)
When Angular needs a dependency, it checks the current injector (e.g., component injector), and if not found it walks up to parent injectors until it finds a provider. If multiple levels provide the same token, the nearest one is used.
Where you provide | Instance behavior | Typical use |
|---|---|---|
| One instance for the whole app (tree-shakeable). | Most services (API clients, facades, shared utilities). |
| New instance per component instance (and its subtree). | Per-screen/wizard state that must reset on destroy. |
Route/environment providers (standalone bootstrap) | Scoped to an environment injector (app-wide or route subtree). | App configuration + feature-level provider scoping in standalone apps. |
import { Component, Injectable, inject } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class UsersService {
getUsers() { return ['Ada', 'Linus']; }
}
@Component({
selector: 'app-users',
standalone: true,
template: `{{ users.join(', ') }}`
})
export class UsersComponent {
// Option A: constructor injection
// constructor(private usersService: UsersService) {}
// Option B: inject() (same timing category as constructor)
private readonly usersService = inject(UsersService);
users = this.usersService.getUsers();
}
Providers: the 4 shapes you must know
These are the practical knobs that control what DI returns for a token.
Provider type | What it does | When to use |
|---|---|---|
| Instantiate a class when the token is requested. | Swap implementations (e.g., mock vs real). |
| Return a constant value/object. | Config objects, feature flags, small immutable values. |
| Call a factory function (can depend on other injections). | Computed config, environment-dependent setup. |
| Alias one token to another (same instance). | Expose one implementation under multiple tokens. |
import { InjectionToken } from '@angular/core';
export type AppConfig = { apiBaseUrl: string };
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');
// Example provider (standalone bootstrap or module providers)
// { provide: APP_CONFIG, useValue: { apiBaseUrl: '/api' } }
import { Component, Inject, Injectable } from '@angular/core';
import { APP_CONFIG, AppConfig } from './app-config.token';
@Injectable({ providedIn: 'root' })
export class ApiClient {
constructor(@Inject(APP_CONFIG) private cfg: AppConfig) {}
url(path: string) {
return `${this.cfg.apiBaseUrl}${path}`;
}
}
@Component({
selector: 'app-demo',
standalone: true,
template: `...`,
providers: [
{ provide: APP_CONFIG, useValue: { apiBaseUrl: '/v2' } }
]
})
export class DemoComponent {
constructor(public api: ApiClient) {}
}
Multi providers (common in real apps)
Some tokens accept multiple values. Angular collects them into an array when multi: true is used (classic example: HTTP interceptors).
import { HTTP_INTERCEPTORS, HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authReq = req.clone({ setHeaders: { Authorization: 'Bearer token' } });
return next(authReq);
};
// provider
// { provide: HTTP_INTERCEPTORS, useValue: authInterceptor, multi: true }
Common pitfall | Symptom | Fix |
|---|---|---|
Accidentally creating multiple instances | Stateful service resets unexpectedly or differs between components. | Understand scope: root vs component providers; avoid providing the same service at many component levels unless you want per-instance state. |
Using the wrong token for non-classes | Runtime DI error: “No provider for X” (or injecting | Use |
Circular dependencies | Runtime error or partially-initialized services. | Refactor responsibilities; extract shared logic; consider factory indirection only as a last resort. |
Optional dependency not marked | DI throws when provider is absent. | Use |
Resolution surprises in a hierarchy | You think you’re getting the root singleton, but a child injector overrides it. | Know “closest provider wins”; use |
Practical scenario
Inject a logging service and an API client across multiple features, then swap a mock in tests.
Common pitfalls
- Providing a service at the wrong level and creating extra instances.
- Circular dependencies causing runtime errors.
- Hard-coding dependencies that make testing hard.
DI improves testability but needs clear provider scope. Test with mocks and verify singleton behavior.
Angular DI resolves dependencies by token using providers in a hierarchical injector tree (closest provider wins). Providers define how to create a value (useClass/useValue/useFactory/useExisting). Where you provide controls lifetime (root singleton vs component-scoped). For non-class deps use InjectionToken. Multi providers collect values into arrays (e.g., interceptors). Common pitfalls are accidental multiple instances, wrong tokens, and circular deps.