Lesson 8 — HTTP and asynchronous state¶
Objective¶
Load untrusted data through a typed repository, model every user-visible async state, and prevent stale or duplicate requests.
Prerequisites¶
DI capabilities, discriminated unions, observable basics, and accessible UI state.
Mental model¶
HttpClient observables are cold: each subscription can issue a request. TypeScript describes compile-time expectations; network bytes remain untrusted. Async UI is a state machine, not one loading boolean.
Why it exists¶
A repository isolates transport/configuration and maps DTOs to trusted domain values. Explicit state keeps loading, empty, error, retry, and success behavior testable.
Working model: subscribe to typed HTTP and render the result.
Engineering reality: subscription owns execution, cancellation, teardown, and error scope. SSR may perform and cache requests in a different lifecycle.
Minimal example¶
export type LoadState<T> =
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; message: string };
Treat successful empty data distinctly in the UI even if it shares the success variant.
Real-world usage¶
APIs require DTO validation, configuration tokens, correlation/timing, retry policy, cancellation, transfer cache awareness, and user recovery.
Common mistakes¶
- trusting generic response types as runtime validation;
- subscribing repeatedly to a cold request;
- swallowing domain errors in a global interceptor;
- nested subscriptions without cancellation;
- collapsing empty, error, and loading into one blank screen.
Mini lab¶
Task: Configure functional HTTP providers/interceptor; create repository interface, HTTP and local-fake implementations; validate/map DTOs; model complete load state; add retry; cancel stale parameter changes.
Constraints: the course must work without a backend, URLs remain behind configuration, and failures stay visible at the owning feature.
Expected result: loading, empty, data, error, and retry are distinct; rapid queries cannot let old responses win.
Hint 1
Separate transport mapping, domain transitions, and rendering decisions.
Hint 2
Move cancellation policy to the stream that turns parameters into requests.
Solution direction — last resort
Keep HTTP details in the repository, expose explicit load state, and use switchMap or a resource boundary for stale reads.
Verification¶
Use provideHttpClientTesting() and HttpTestingController to verify method, URL, mapping, error, and outstanding requests. Throttle the network and reproduce stale search before fixing it.
Mastery evidence: explain duplicate cold requests, reject malformed data at the boundary, and prove stale cancellation.
Bloom challenge¶
- Understand: cold request and subscription ownership.
- Apply: test success, empty, and error.
- Analyze: diagnose old responses winning.
- Evaluate: interceptor versus repository responsibility.
- Create: design optimistic updates with rollback and version conflict handling.
Summary¶
HTTP types are promises to the compiler, not runtime proof. Own execution and failure explicitly at transport, domain, and UI boundaries.
Spaced review¶
Retrieve route resolver tradeoffs from Lesson 6. Lesson 9 makes concurrency policy explicit; Lesson 12 revisits transfer cache and observability.
What comes next¶
Continue to RxJS reasoning.