Lesson 5 — Dependency injection and lifetime¶
Objective¶
Separate domain state, persistence, and configuration with testable capabilities and intentional provider scopes.
Prerequisites¶
Signal-store ownership, TypeScript interfaces, and the application/route component tree.
Mental model¶
A token identifies a dependency, a provider describes how to supply it, and an injector resolves and caches it within a hierarchy. Scope determines identity and lifetime.
Why it exists¶
DI lets consumers request capabilities without constructing infrastructure and lets applications choose implementations at composition boundaries.
Working model: Angular finds the nearest provider for a token.
Engineering reality: environment and element injector hierarchies interact; moving a provider can create a new instance, reset state, alter cleanup, or access browser-only factories during SSR.
Minimal example¶
export const STORAGE = new InjectionToken<Storage>('Browser storage', {
providedIn: 'root',
factory: () => localStorage
});
Treat that browser factory as a deliberate SSR question, not a universally safe default.
Real-world usage¶
Repositories, configuration, clocks, storage, validators, and feature stores gain replaceable implementations and controlled lifetimes.
Common mistakes¶
- treating
providedIn: 'root'as automatically correct; - providing a store again in a leaf component;
- injecting giant environment objects instead of capabilities;
- hiding manual construction inside consumers;
- invoking browser globals in server contexts.
Mini lab¶
Task: Keep TrackerStore focused on domain transitions. Define a storage token, create a session repository, inject dependencies, override storage in tests, and move the store temporarily between root and route scopes.
Constraints: no real browser storage in repository tests and no accidental duplicate store provider.
Expected result: consumers inside one boundary share an instance; separate route boundaries do not; navigation demonstrates intentional reset or persistence.
Hint 1
Draw the injector tree before moving providers.
Hint 2
Choose root, parent route, route, or component based on required sharing and destruction.
Solution direction — last resort
Inject a capability-oriented token into the repository and override it with a fake. Place the store at the narrowest boundary that satisfies cross-route persistence.
Verification¶
Add providers: [TrackerStore] to a leaf, observe updates disappear elsewhere, trace resolution, and fix the narrowest scope. Explain useClass, useValue, useFactory, and useExisting with one justified use each.
Mastery evidence: predict service identity from the injector tree and replace browser infrastructure in a test.
Bloom challenge¶
- Understand: token, provider, and injector.
- Apply: override storage with a fake.
- Analyze: trace a duplicate service instance.
- Evaluate: root versus route provider lifetime.
- Create: design an SSR-safe, multi-provider validation capability.
Summary¶
Provider location is an architecture decision about implementation, identity, sharing, and cleanup.
Spaced review¶
Connect Lesson 1's root environment injector to this route scope. Lesson 12 revisits browser-only dependencies under SSR.
What comes next¶
Continue to routing.