38. NgRx: Введение
🏪 NgRx — Введение. Redux-паттерн в Angular
Заголовок раздела «🏪 NgRx — Введение. Redux-паттерн в Angular»NgRx — это реализация Redux-паттерна для Angular на базе RxJS. Это не просто библиотека, это архитектурное решение для управления состоянием крупных Angular-приложений. Когда данные текут через всё приложение, и ты уже запутался — кто их обновляет и откуда — NgRx приходит на помощь 🏛️
Проблема: почему нужен NgRx?
Заголовок раздела «Проблема: почему нужен NgRx?»Представь приложение с 50+ компонентами. Данные о текущем пользователе нужны в хедере, в боковом меню, в настройках профиля и ещё в 10 местах. Без централизованного состояния это превращается в ад:
// Без NgRx — данные передаются через @Input/@Output цепочки// или через сервисы с BehaviorSubject// Это работает, но становится неуправляемым при росте
@Component({}) // Headerexport class HeaderComponent { user$ = this.userService.user$; // Каждый компонент подписывается сам}
@Component({}) // Sidebarexport class SidebarComponent { user$ = this.userService.user$; // И изменяет состояние сам updatePermissions() { this.userService.updateUser(...); // Кто вызвал это? Когда? Зачем? }}Главные проблемы:
- 🔀 Изменения состояния происходят в разных местах — сложно отследить
- 🐛 Баги сложно воспроизвести — нет истории изменений
- 🧪 Компоненты со сложной логикой сложно тестировать
- 🔄 Синхронизация состояния между компонентами — головная боль
Redux-паттерн: идея в трёх словах
Заголовок раздела «Redux-паттерн: идея в трёх словах»Redux — это паттерн: одно состояние → чистые функции → неизменяемость
UI (Component) ↓ dispatch(Action)Actions (Что произошло?) ↓Reducers (Как изменить состояние?) ↓Store (Единственный источник правды) ↓ select(Selector)UI (Component) ← получает новое состояниеТри закона Redux
Заголовок раздела «Три закона Redux»- Единственный источник правды — всё состояние приложения в одном Store
- Состояние только для чтения — изменить состояние можно только через dispatch(Action)
- Чистые функции — Reducers — это
(state, action) => newStateбез побочных эффектов
Ключевые концепции NgRx
Заголовок раздела «Ключевые концепции NgRx»Store — хранилище состояния
Заголовок раздела «Store — хранилище состояния»// Весь AppState выглядит так:interface AppState { users: UsersState; auth: AuthState; router: RouterReducerState; ui: UiState;}
// Это объект в памяти, доступный из любого места через inject(Store)Actions — события
Заголовок раздела «Actions — события»// Action — это просто объект с полем type// NgRx: createAction создаёт type-safe action creatorimport { createAction, props } from '@ngrx/store';
// Без данныхexport const loadUsers = createAction('[Users] Load Users');
// С даннымиexport const loadUsersSuccess = createAction( '[Users] Load Users Success', props<{ users: User[] }>());
export const loadUsersFailure = createAction( '[Users] Load Users Failure', props<{ error: string }>());Соглашение по именованию Action:
[Источник] Описание события[Users Page] Load Users[Auth API] Login Success[Router] Navigation EndReducers — функции изменения состояния
Заголовок раздела «Reducers — функции изменения состояния»import { createReducer, on } from '@ngrx/store';
interface UsersState { users: User[]; loading: boolean; error: string | null;}
const initialState: UsersState = { users: [], loading: false, error: null,};
export const usersReducer = createReducer( initialState, on(loadUsers, state => ({ ...state, loading: true, error: null })), on(loadUsersSuccess, (state, { users }) => ({ ...state, users, loading: false, })), on(loadUsersFailure, (state, { error }) => ({ ...state, error, loading: false, })));Selectors — запросы к состоянию
Заголовок раздела «Selectors — запросы к состоянию»import { createFeatureSelector, createSelector } from '@ngrx/store';
// Выбираем кусок состоянияconst selectUsersState = createFeatureSelector<UsersState>('users');
// Делаем производный селекторexport const selectAllUsers = createSelector( selectUsersState, state => state.users);
export const selectUsersLoading = createSelector( selectUsersState, state => state.loading);Effects — побочные эффекты
Заголовок раздела «Effects — побочные эффекты»import { Injectable } from '@angular/core';import { Actions, createEffect, ofType } from '@ngrx/effects';import { switchMap, map, catchError } from 'rxjs/operators';import { of } from 'rxjs';
@Injectable()export class UsersEffects { loadUsers$ = createEffect(() => this.actions$.pipe( ofType(loadUsers), switchMap(() => this.userService.getAll().pipe( map(users => loadUsersSuccess({ users })), catchError(error => of(loadUsersFailure({ error: error.message }))) ) ) ) );
constructor( private actions$: Actions, private userService: UserService ) {}}NgRx DevTools
Заголовок раздела «NgRx DevTools»NgRx DevTools — это расширение для Chrome, которое позволяет:
- Видеть все dispatched actions в реальном времени
- Путешествовать во времени (time-travel debugging)
- Отслеживать изменения состояния
- Импортировать/экспортировать состояние
import { provideStoreDevtools } from '@ngrx/store-devtools';
export const appConfig: ApplicationConfig = { providers: [ provideStore(reducers), provideStoreDevtools({ maxAge: 25, logOnly: environment.production, // В продакшене только логирование autoPause: true, }) ]};Когда использовать NgRx? 🤔
Заголовок раздела «Когда использовать NgRx? 🤔»NgRx — мощный инструмент, но он добавляет сложность. Используй правило SHARI:
| Буква | Принцип | Пример |
|---|---|---|
| Shared | Состояние используется в нескольких несвязанных компонентах | Данные пользователя |
| Hydrated | Состояние нужно сохранить при навигации | Фильтры таблицы |
| Available | Состояние доступно при входе в приложение | Конфигурация |
| Retrieved | Состояние загружается с сервера | Список продуктов |
| Impacted | Состояние влияет на другие части приложения | Корзина покупок |
НЕ используй NgRx, если:
- Состояние используется только в одном компоненте →
signal()или локальный state - Маленькое приложение → сервис с
BehaviorSubject - Данные не нужно синхронизировать между страницами →
ComponentStore
”Письмо команде” 📨
Заголовок раздела «”Письмо команде” 📨»Привет, команда!
NgRx — это как строгий бухгалтер в офисе.
Раньше каждый мог взять деньги из кассы (изменить состояние)когда хотел и как хотел. Было удобно, но никто не знал,сколько денег осталось и куда они делись.
Теперь:1. Ты хочешь взять деньги? Заполни заявку (dispatch Action)2. Бухгалтер проверяет заявку по правилам (Reducer)3. Деньги выдаются строго по регламенту (новый State)4. Всё записывается в журнал (DevTools)5. Если нужно перевести деньги из банка — есть отдел по работе с банками (Effects)
Да, немного бюрократии. Но зато:— Всегда знаешь, что происходит— Можно откатиться назад— Легко находить баги
С уважением,NgRx архитектура