Skip to content

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

  1. Understand: cold request and subscription ownership.
  2. Apply: test success, empty, and error.
  3. Analyze: diagnose old responses winning.
  4. Evaluate: interceptor versus repository responsibility.
  5. 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.