Basics
Dependency injection deep dive
Injector hierarchy, provider scopes, InjectionToken, and resolution modifiers.
Angular DI resolves a dependency by walking a hierarchy of injectors: the element injector chain (component tree) first, then the module/environment injector. The first provider found wins, which is how you override a service for a subtree.
The injector hierarchy is a chain of managers: you ask your team lead first, and the request escalates up the org chart until someone can fulfil it.
Key concepts
1
providedIn: 'root' makes a service a tree-shakeable singleton; providing it in a component’s providers array creates a new instance per component subtree — the basis of scoped state. Non-class dependencies (config, strings) use an InjectionToken.
tree-shakeable singletonnew instance per component subtree`InjectionToken`providedIn: 'root'providers
2
Resolution modifiers tune the lookup: @Optional() returns null instead of throwing, @Self() restricts to the local injector, @SkipSelf() starts at the parent, and @Host() stops at the host component.
Resolution modifiers@Optional()@Self()@SkipSelf()@Host()
3
Pitfall: accidentally re-providing a root service in a component resets its state for that subtree. Interview follow-up: "how would you provide different implementations of the same interface?" — an InjectionToken plus a factory or useClass/useExisting provider.
Pitfall:Interview follow-up:InjectionTokenuseClassuseExisting
typescript
export const API_URL = new InjectionToken<string>('API_URL');
@Component({
selector: 'app-widget',
standalone: true,
providers: [{ provide: API_URL, useValue: '/api/v2' }], // scoped override
})
export class WidgetComponent {
private url = inject(API_URL);
private logger = inject(Logger, { optional: true }); // @Optional
}