The Strategy Pattern lets us encapsulate alternative flows behind a common interface. Instead of scattering if/else logic through components, each algorithm lives in its own class, and the component delegates to the selected strategy at runtime. This keeps UI code clean, makes the system extensible, and lets us vary behavior via DI configuration.
Two screens to compare approaches:
- Without Strategy (default route): a component branches on an input
kindand mixes orchestration with UI state. - With Strategy : the same UI delegates all orchestration to pluggable strategies resolved via a registry and DI. The component can still accept a
kindstring, or a concrete strategy instance (great for unit tests).
npm install
npm startThen open http://localhost:4200
- Create
new-flow.strategy.tsimplementingRecordingStrategy. - Bind it in
app.config.ts:
provideRecordingStrategyBindings([
// ...
{ kind: DemoKind.YourNewKind, strategy: YourNewStrategy }
]);- Use it in the page:
<app-recorder-with title="Your New Flow" [strategy]="DemoKind.YourNewKind"></app-recorder-with>No component churn required.
This diagram maps the roles and relationships in our demo app—what talks to what, and why. It’s not step‑by‑step code; it’s the architecture at a glance.
-
Boxes are the key building blocks:
- Components (UI parts you see on screen).
- Strategies (pluggable orchestration for recordings).
- Helpers/services (shared utilities and base classes).
-
Lines show relationships:
- A plain arrow means “uses” (depends on).
- A triangle arrow means “is a kind of” (inheritance/implementation).
Two separate components — RecorderDirectUploadComponent and RecorderModalConfirmComponent — each own their orchestration (capture → maybe confirm → upload). Logic is duplicated; adding flows multiplies components and tests.
- Pros: simple to start.
- Cons: duplication, test drift, harder refactors, component churn for every new flow.
The UI component RecorderWithComponent delegates to a strategy chosen via DI (StrategyRegistry). Strategies (DirectUploadStrategy, ModalConfirmStrategy) inherit shared logic from the abstract RecordAndUploadDirective, keeping components lean and testable.
- Pros: pluggable flows, thin UI, fast tests, add flows via a class + binding.
- Trade‑offs: a tiny bit of upfront structure (interfaces + DI registry).
DemoContext (src/app/shared/models.ts) is the data + callbacks a strategy needs:
title,kindsetStatus,setProgressenqueue(id)— remains the last event (so tests can assert end‑of‑flow)
Strategies compose simple services:
CaptureService– simulates capture timingUploadService– emits 0→1 progressConfirmService– yes/no confirmation
StrategyRegistry maps a “kind” to its strategy via DI bindings (see app.config.ts). Add a new flow by binding a new strategy class; no UI churn.
%% Mermaid class diagram for this project
classDiagram
%% UI Components
class RecorderWithComponent {
+title: string
+strategy?: DemoKind | RecordingStrategy
+start(): Promise<void>
+reset(): void
-resolve(): RecordingStrategy
-status: DemoStatus
-progress: number
}
class RecorderDirectUploadComponent {
+title: string
+start(): Promise<void>
+reset(): void
-status: DemoStatus
-progress: number
}
class RecorderModalConfirmComponent {
+title: string
+start(): Promise<void>
+reset(): void
-status: DemoStatus
-progress: number
}
class PageWithoutStrategyPage
class PageWithStrategyPage
%% Strategy contracts & registry
class RecordingStrategy {
<<interface>>
+run(ctx: DemoContext) Promise<void>
}
class StrategyRegistry {
+getByKind(kind: DemoKind): RecordingStrategy
+getDefault(): RecordingStrategy
}
%% Base directive (shared logic)
class RecordAndUploadDirective {
<<abstract>>
#captureOnce(ctx: DemoContext) Promise~string~
#uploadWithProgress(ctx: DemoContext, path: string) Promise~void~
#finalize(ctx: DemoContext, enqueueId: string) void
}
%% Concrete strategies
class DirectUploadStrategy {
+run(ctx: DemoContext) Promise~void~
}
class ModalConfirmStrategy {
+run(ctx: DemoContext) Promise~void~
}
%% Services & models
class CaptureService {
+startRecording(): Promise~string~
}
class UploadService {
+upload(path: string): Observable~number~
}
class ConfirmService {
+confirm(title: string): Promise~boolean~
}
class DemoContext {
+title: string
+kind: DemoKind
+setStatus(s: DemoStatus): void
+setProgress(p: number): void
+enqueue(id: string): void
}
%% Relationships (uses, implementation, inheritance)
PageWithoutStrategyPage --> RecorderDirectUploadComponent
PageWithoutStrategyPage --> RecorderModalConfirmComponent
PageWithStrategyPage --> RecorderWithComponent
RecorderWithComponent --> StrategyRegistry
StrategyRegistry --> RecordingStrategy
RecordingStrategy <|.. DirectUploadStrategy
RecordingStrategy <|.. ModalConfirmStrategy
RecordAndUploadDirective <|-- DirectUploadStrategy
RecordAndUploadDirective <|-- ModalConfirmStrategy
DirectUploadStrategy --> CaptureService
DirectUploadStrategy --> UploadService
ModalConfirmStrategy --> CaptureService
ModalConfirmStrategy --> UploadService
ModalConfirmStrategy --> ConfirmService
src/app/components/
recorder-direct-upload/… # without-strategy UI: direct upload
recorder-modal-confirm/… # without-strategy UI: modal confirm
recorder-with/… # with-strategy UI (delegates to DI)
src/app/core/recording/
strategies/
direct-upload.strategy.ts # concrete strategy
modal-confirm.strategy.ts # concrete strategy
record-and-upload.directive.ts # abstract base with shared steps
recording-strategy.registry.ts # DI registry & bindings
recording-strategy.types.ts # interface contracts
src/app/core/services/
capture/capture.service.ts # fake capture
upload/upload.service.ts # progress stream
confirm/confirm.service.ts # confirm dialog
src/app/features/
page-without-strategy/… # demo page: without strategy
page-with-strategy/… # demo page: with strategy
- Problem: duplicated orchestration across components; branching by “kind”; test drift.
- Solution: Strategy pattern + DI registry + shared base directive → pluggable flows, thin components.
- Outcome: add a new flow by adding a class and a binding—no UI churn, simpler testing.
- Strategy Pattern (Wikipedia)
- SourceMaking - Strategy Pattern
- Refactoring Guru - Strategy Pattern
- Refactoring Guru - Strategy Pattern (TypeScript)
- Angular - Dependency Injection Providers
- Angular - Dependency Injection
- Medium - Strategy Design Pattern in TypeScript
- Medium - Implementing Strategy Pattern in TypeScript