๐Ÿณ IntuitiveFE
Login
โ† All concepts

Angular Dependency Injection

โฑ๏ธ ~5-minute bite ยท solve the sandbox to master

0%lesson
๐Ÿง’

5-Year-Old Metaphor

โ€” The physical, real-world picture. No jargon.

๐Ÿช DI is a city-wide supply chain where the nearest warehouse ships first. If your local corner store has milk (component provider), you buy there. If not, go to the regional hub (parent), then the city warehouse (root). Root always has stock because it serves everyone.

๐Ÿ™๏ธ

Root injector

providedIn: 'root' โ€” one singleton for the whole app. Tree-shakeable.

๐Ÿ 

Component injector

providers: [] in @Component โ€” new instance per component. Destroyed with component.

๐Ÿช™

Token-based

InjectionToken<T> โ€” typed tokens for non-class values: config, arrays, primitives.

inject() โ€” the modern alternative to constructor injection

js
1// Constructor injection (classic)
2constructor(private auth: AuthService, private router: Router) {}
3ย 
4// inject() โ€” works anywhere in injection context (Angular 14+)
5export class MyComponent {
6 private auth = inject(AuthService);
7 private router = inject(Router);
8 private config = inject(APP_CONFIG); // InjectionToken
9 private opt = inject(SomeService, { optional: true }); // optional
10}
11ย 
12// Also works in standalone functions (great for composable hooks)
13export function useAuth() {
14 const auth = inject(AuthService);
15 const router = inject(Router);
16 return { login: auth.login.bind(auth), goHome: () => router.navigate(['/']) };
17}
๐ŸŽ›๏ธ

Interactive Sandbox

โ€” Move something, see it react instantly.

Pattern

Injector tree โ€” toggle which level provides MyService

โœ“ ChildComponent gets MyService from: Root Injector

When to use

For stateful services that should be shared globally: auth, HTTP clients, logging, analytics, feature flags. providedIn: 'root' is the default choice for most services.

providedIn: 'root' โ€” code example
js
1import { Injectable } from '@angular/core';
2ย 
3// providedIn: 'root' is the most common pattern
4// One instance for the entire app โ€” shared state
5@Injectable({ providedIn: 'root' })
6export class AuthService {
7 private currentUser: User | null = null;
8ย 
9 login(creds: Credentials): Observable<User> { /* ... */ }
10 logout(): void { this.currentUser = null; }
11 getUser(): User | null { return this.currentUser; }
12}
13ย 
14// Usage โ€” Angular injects the singleton automatically
15@Component({ standalone: true, template: '' })
16export class HeaderComponent {
17 constructor(private auth: AuthService) {}
18 // OR modern functional injection (Angular 14+):
19 // private auth = inject(AuthService);
20}
21ย 
22// Also available: providedIn: 'platform' โ€” shared across apps in a
23// multi-app shell. providedIn: 'any' โ€” unique instance per lazy module.
Gotcha: A root-provided service is never destroyed โ€” its state persists for the lifetime of the app. For services that should reset (e.g., per-session state), use component-level providers or manually reset in ngOnDestroy.
Insight: Tree-shaking works because the service references the module (not vice versa). The injector only adds the service to the bundle if something actually imports and injects it โ€” unlike NgModule-based providers which always bundle.
Explored 1/5:๐ŸŒ๐Ÿ ๐ŸŒฒ๐Ÿ”„๐Ÿช™
๐ŸŽฏ

Challenge

Explore all 5 DI patterns. Use the tree visualizer to understand how Angular resolves MyService at each level.

Try it
๐ŸŽฏ

Why Should I Care?

โ€” The exact interview question + the bug it kills.

Interview questions

Q: What's the difference between providedIn: 'root' and component-level providers?

providedIn: 'root' creates one singleton shared across the entire app โ€” it's tree-shakeable and lives for the app lifetime. Component providers: [] create a new instance for each component instance โ€” scoped to that component's subtree and destroyed when the component is destroyed. Use root for shared state (auth, HTTP), use component providers for instance-specific state (form wizard state, per-widget state).

Q: Why use InjectionToken instead of a class?

Classes can only be injected as-is. InjectionToken works for non-class values: config objects, strings, booleans, factory functions, arrays (multi: true). It's also the correct tool when you want multiple implementations of the same interface โ€” TypeScript interfaces are erased at runtime so they can't serve as DI tokens. InjectionToken provides a stable runtime identity independent of any class.

Q: What is the Angular injector hierarchy?

Angular has two parallel injector trees. The element injector tree mirrors the component tree โ€” each component has its own injector that checks its providers: [] first. The environment injector tree covers NgModules and ApplicationConfig-level providers. When a component requests a service, Angular walks the element injector tree up to root, then up the environment injector chain to the platform injector. If still not found: NullInjectorError.

๐Ÿ”ฌ

The Deep Dive

โ€” Spec refs, engine internals, the minutiae.

providedIn variants โ€” 'root' vs 'platform' vs 'any'

'root'Most common. One singleton per application. Tree-shakeable. The default choice.
'platform'Shared across multiple Angular applications running in the same page (micro-frontend shell). Rare.
'any'Unique instance per lazy-loaded module. Deprecated in Angular 16 โ€” avoided for new code.
NgModule classprovidedIn: SomeModule โ€” scoped to that NgModule. Works in legacy codebases.

useFactory with deps โ€” dynamic service creation

js
1// Factory provider โ€” full control over instantiation
2{
3 provide: DataService,
4 useFactory: (http: HttpClient, config: AppConfig) => {
5 return config.useMock
6 ? new MockDataService()
7 : new DataService(http, config.apiBaseUrl);
8 },
9 deps: [HttpClient, APP_CONFIG], // injected as factory arguments
10}
11ย 
12// Multi providers โ€” aggregate multiple values under one token
13export const HTTP_INTERCEPTORS = new InjectionToken<HttpInterceptor[]>(
14 'HTTP_INTERCEPTORS'
15);
16providers: [
17 { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
18 { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
19 // inject(HTTP_INTERCEPTORS) โ†’ [AuthInterceptor, LoggingInterceptor]
20]

inject() in standalone contexts

inject() works in injection context: inside class constructors, class field initializers, provider factories, and Angular hooks. It's the enabling primitive for composable utility functions โ€” extracting DI logic out of constructors into reusable functions, similar to React hooks. Key: inject() throws if called outside an injection context (e.g., inside setTimeout or async callbacks).

js
1// Composable DI utility โ€” works like a custom React hook
2export function injectBreakpoint() {
3 const breakpointObserver = inject(BreakpointObserver);
4 return breakpointObserver.observe([Breakpoints.Handset]).pipe(
5 map((state) => state.matches),
6 );
7}
8ย 
9// Used in any component or service
10@Component({ template: '' })
11export class ResponsiveComponent {
12 isMobile$ = injectBreakpoint(); // inject() called during field init
13}
๐ŸŽค

Interview Questions

โ€” Real questions from real interviews โ€” with answers.

Root = app-wide singleton; component providers = new instance per component instance, destroyed with it.

InjectionToken works for non-class values (configs, arrays, primitives) and avoids TypeScript interface erasure.

Element injectors (child โ†’ parent โ†’ ... โ†’ root) first, then environment injectors, then NullInjector.

inject() works in field initializers and standalone functions โ€” enabling composable utility hooks.

useFactory: custom instantiation logic or dynamic choice of implementation. useExisting: alias one token to another.

Lazy routes create their own environment injector โ€” services provided there are NOT shared with root.

๐ŸŽฎ

Memory Game

โ€” Quick quiz โ€” lock the concept in long-term memory.
1/4

What is the platform injector in Angular's DI hierarchy?