One resource object instead of three separate fields (isLoading, data, error). The loader takes a request and returns a value. Between them — loading. On failure — error. Everything tied to the current request.
The problem it solves
Store loading/data/error separately and sooner or later they drift. One handler resets loading, another forgets to clear error. The UI ends up in an impossible combination: loading=true and data=present.
Operators and why they matter
startWith({loading: true}) — show loading immediately, before the first response.
switchMap — a new request cancels the old load.
map — turn a successful response into a value state.
catchError — a failure becomes an error state WITHOUT killing the outer stream. You can issue a new request.
Gotchas
mergeMap — an old request's response can arrive after a new one and overwrite the UI.
Don't store loading separately — it's DERIVED from the stream (true before the first next, false after).
The default value must be a deliberate choice. Otherwise the UI can't tell "empty result" from "not loaded yet".
What you get
Async state becomes one explicit resource model. No impossible flag combinations.