Skip to content

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

  1. Understand: token, provider, and injector.
  2. Apply: override storage with a fake.
  3. Analyze: trace a duplicate service instance.
  4. Evaluate: root versus route provider lifetime.
  5. 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.