State Management
NgRx и SignalStore (Angular)
Angular-команда строит личный кабинет банка. Корзина действий, история операций, статус загрузки выписки. Сначала всё держится в сервисах с BehaviorSubject, но через год потоки RxJS переплетаются так, что отладка занимает часы. Команда переходит на NgRx: каждое изменение это action, состояние пересобирает чистый reducer, побочные эффекты вынесены в effects. Через несколько лет Angular добавляет сигналы, и появляется SignalStore - тот же предсказуемый стор, но без церемоний RxJS вокруг простого чтения значения.
- Крупные корпоративные SPA на Angular (банки, страховые, ERP), где аудит каждого изменения состояния важен
- Дашборды с десятками панелей, где статус загрузки и кэш данных живут в едином сторе
- Команды, мигрирующие со связки сервис плюс BehaviorSubject на NgRx ради предсказуемого потока
- Новые Angular-проекты на SignalStore, где сигналы убирают часть boilerplate классического NgRx
- Интеграция NgRx Effects с HTTP и WebSocket для серверных побочных эффектов
Предварительные знания
- Идея однонаправленного потока Flux: action меняет состояние через reducer
- Понимание чистой функции reducer: (state, action) даёт новое состояние без мутаций
- Базовое представление о реактивности и подписке на поток значений
NgRx: Redux на Angular
NgRx переносит модель Redux в экосистему Angular. Состояние приложения хранится в едином store, изменения описываются через action, а новое состояние собирает чистая функция reducer. Поток строго однонаправленный: компонент диспатчит action, reducer пересчитывает состояние, подписчики получают обновление. Тот же Flux, что и в React-мире, но опирающийся на RxJS как на транспорт реактивности.
Action описывает намерение через строковый тип и опциональный payload. Reducer сопоставляет action с обработчиком через on и возвращает новое состояние без мутации старого. Структура повторяет Redux один в один, поэтому понимание Flux переносится сюда напрямую.
NgRx появился в 2016 году как порт Redux на Angular. Именно строгий поток и развитые devtools сделали его стандартом для крупных Angular-приложений, где важно отследить каждое изменение состояния по истории action.
Что в NgRx отвечает за пересчёт состояния в ответ на action?
Effects и selectors
Reducer обязан быть чистым, поэтому работе с сетью в нём не место. Эту задачу решают effects. Effect слушает поток action, выполняет побочный эффект (запрос к API, работа с WebSocket) и обычно диспатчит новый action с результатом. Так HTTP и асинхронность изолированы в отдельном слое на RxJS, а reducer остаётся предсказуемым.
Чтение состояния идёт через selectors. Selector это мемоизированная функция, которая выбирает срез store и пересчитывается только при изменении входных данных. Компонент подписывается на конкретный selector и обновляется лишь тогда, когда изменился именно его срез.
Селекторы можно собирать из других селекторов. selectIsPositive строится поверх selectCount и пересчитывается только при смене count. Это та же идея узкой подписки, что и селекторы в Zustand, но с встроенной мемоизацией.
Почему запрос к API выносят в effect, а не делают прямо в reducer?
SignalStore: стор на сигналах
С приходом сигналов в Angular NgRx выпустил SignalStore. Это стор, где состояние выражено сигналами, а не RxJS-потоками. Для чтения значения больше не нужен async-pipe или подписка: сигнал читается синхронно прямо в шаблоне. Boilerplate классического NgRx (отдельные файлы action, reducer, effects) сжимается до компактного описания стора.
Состояние описывается через withState, методы через withMethods, а patchState обновляет срез. Внутри компонента count читается как store.count() - обычный сигнал. Производные значения строятся через computed, побочные эффекты подключаются отдельным блоком. Модель ближе к Zustand: данные и методы в одном месте, без обширной обвязки.
- Классический NgRx Store — Action, reducer, effects, selectors в отдельных слоях. Строгий аудит через историю action и развитые devtools. Уместен в крупных приложениях, где важна прослеживаемость каждого изменения
- SignalStore — Состояние как сигналы, чтение без подписки и async-pipe, минимум boilerplate. Уместен в новых проектах на сигналах, где нужен предсказуемый стор без церемоний RxJS вокруг простого чтения
Оба подхода живут под зонтиком NgRx и могут сосуществовать. Полный разбор API, настройки и тестирования обоих сторов идёт в курсе по Angular - здесь дан обзор, чтобы видеть их место на карте state management.
Чем SignalStore отличается от классического NgRx Store при чтении состояния в шаблоне?
Связь с другими темами
Этот урок даёт обзор. Полный разбор Angular-инструментов идёт в профильном курсе:
- NgRx Store, Effects и SignalStore в Angular — Реальный код, настройка стора, тестирование effects и миграция на сигналы разобраны в курсе по Angular
- Flux и reducer — NgRx это прямое воплощение Flux на Angular: action, reducer и однонаправленный поток уже знакомы
Итог
- NgRx переносит модель Redux в Angular: store, actions, reducers, selectors и effects для побочных эффектов
- Reducer остаётся чистой функцией, effects изолируют работу с HTTP и WebSocket в отдельный слой на RxJS
- Selectors дают мемоизированный доступ к срезам состояния, чтобы компоненты подписывались только на нужное
- SignalStore это новый подход NgRx поверх Angular Signals: меньше boilerplate, чтение состояния как сигнала
- Классический NgRx уместен в крупных приложениях с акцентом на аудит и devtools, SignalStore - в новых проектах на сигналах
- Детали API, настройка и тестирование разбираются в курсе по Angular, здесь дан обзор для сравнения подходов
Связанные уроки
- sm-07-flux-reducer — NgRx это Flux/Redux на Angular: те же action, reducer и однонаправленный поток, разобранные на уровне идеи в прошлом уроке
- ng-39-ngrx-signalstore — Полный разбор NgRx Store, Effects и SignalStore с реальным кодом Angular живёт в курсе по Angular