Один resource-объект вместо трёх раздельных полей (isLoading, data, error). Loader получает request, отдаёт value. Между ними — loading. При ошибке — error. Всё связано с текущим request.
Какую проблему решаем
Если хранить loading/data/error отдельно — рано или поздно они разойдутся. Один обработчик сбросит loading, другой забудет очистить error. UI окажется в невозможной комбинации: loading=true и data=present.
Операторы и зачем они нужны
startWith({loading: true}) — сразу показываем loading, до первого ответа.
switchMap — новый request отменяет загрузку старого.
map — успешный ответ превращаем в value state.
catchError — failure становится error state БЕЗ падения outer stream. Можно сделать новый request.
Подводные камни
mergeMap — ответ старого request может прийти после нового и перезаписать UI.
Не храните loading отдельно — оно ВЫЧИСЛЯЕТСЯ из stream (true до первого next, false после).
default value должен быть осознанным выбором. Иначе UI не отличит «пустой результат» от «ещё не загружено».
Что в итоге получаем
Async-состояние превращается в одну явную resource-модель. Никаких невозможных комбинаций флагов.