Использование NgRx для state-менеджмента в Angular
State-менеджмент — одна из ключевых задач при разработке современных веб-приложений на Angular. С ростом приложения и увеличением количества взаимодействующих компонент становится всё сложнее контролировать состояние, передавать данные и управлять их изменениями. NgRx предлагает эффективное и масштабируемое решение, основанное на концепциях Redux, для централизованного управления состоянием в Angular-приложениях.
Что такое NgRx и зачем он нужен
NgRx — это библиотека для управления состоянием, созданная специально для Angular. Она использует потоковое программирование и паттерн Redux, обеспечивая однонаправленный поток данных и предсказуемость изменения состояния. Основная цель NgRx — сделать управление сложным состоянием приложения удобным, чистым и устойчивым к ошибкам.
При помощи NgRx разработчики могут централизовать состояние всего приложения в едином хранилище (Store). Это улучшает масштабируемость проекта, упрощает дебаггинг и облегчает тестирование. Кроме того, благодаря усиленной типизации и строгим правилам изменения состояния, повышается качество кода.
Основные преимущества NgRx
- Единый источник правды для всего состояния приложения.
- Предсказуемые изменения состояния с помощью чистых функций (редьюсеров).
- Встроенная поддержка side-эффектов через эффекты (Effects).
- Высокий уровень типизации с TypeScript.
- Отличная поддержка инструментов разработчика для отладки.
- Гибкая и расширяемая архитектура.
Основные концепции NgRx
Для эффективного использования NgRx необходимо понимать его ключевые концепции и компоненты. Они формируют каркас, на котором строится система управления состоянием приложения.
NgRx основан на паттерне Redux, но внедрён таким образом, чтобы максимально использовать возможности Angular и RxJS. Ниже представлены основные блоки, составляющие архитектуру NgRx:
Store (хранилище)
Store — это объект, который содержит всё состояние приложения в виде единой структуры данных. Вся информация, необходимая для отображения пользовательского интерфейса и логики приложения, хранится здесь. Компоненты и сервисы могут подписываться на изменения состояния, получая обновлённые данные.
Actions (действия)
Actions — это простые объекты с типом и, при необходимости, дополнительными данными, которые описывают событие или намерение изменить состояние. Все изменения будут инициированы через действия, что обеспечивает прозрачность и возможность отслеживания истории изменений.
Reducers (редьюсеры)
Редьюсеры — чистые функции, которые принимают текущее состояние и действие, и возвращают новое состояние. Они определяют, как именно состояние меняется в ответ на определённые действия. Редьюсеры должны быть иммутабельными, не изменяя старое состояние напрямую.
Effects (эффекты)
Effects используются для обработки побочных эффектов, таких как асинхронные запросы к серверу, взаимодействия с API, навигация и другие операции, которые не должны напрямую изменять состояние. Они реагируют на действия и могут инициировать новые действия после выполнения задачи.
Установка и настройка NgRx в Angular проекте
Чтобы начать использовать NgRx, необходимо установить несколько пакетов, обеспечивающих работу со стором, эффектами и дополнениями. Для этого достаточно выполнить несколько команд с помощью npm или yarn.
После установки следует добавить StoreModule и EffectsModule в корневой модуль приложения, настроив их на работу с редьюсерами и эффектами.
Шаги установки
- Установка основных пакетов:
npm install @ngrx/store @ngrx/effects @ngrx/store-devtools
- Импорт StoreModule и регистрация корневого редьюсера:
import { StoreModule } from '@ngrx/store'; import { reducers } from './store/reducers'; @NgModule({ imports: [ StoreModule.forRoot(reducers) ] }) export class AppModule { } - Подключение EffectsModule для работы с побочными эффектами:
import { EffectsModule } from '@ngrx/effects'; import { UserEffects } from './store/effects/user.effects'; @NgModule({ imports: [ EffectsModule.forRoot([UserEffects]) ] }) export class AppModule { } - Подключение StoreDevtoolsModule для отладки:
import { StoreDevtoolsModule } from '@ngrx/store-devtools'; @NgModule({ imports: [ StoreDevtoolsModule.instrument({ maxAge: 25 }) ] }) export class AppModule { }
Пример реализации: управление списком задач
Рассмотрим на практике, как организовать state-менеджмент с помощью NgRx для простого списка задач (to-do list). Основной функционал включает добавление задач, изменение их состояния и удаление.
Определение модели и действий
Сначала создадим интерфейс задачи и установить набор дейсвий, которые можно выполнять с задачами.
export interface Task {
id: string;
title: string;
completed: boolean;
}
import { createAction, props } from '@ngrx/store';
export const addTask = createAction(
'[Task] Add Task',
props<{ task: Task }>()
);
export const toggleTask = createAction(
'[Task] Toggle Task',
props<{ id: string }>()
);
export const removeTask = createAction(
'[Task] Remove Task',
props<{ id: string }>()
);
Создание редьюсера
Далее реализуем редьюсер для обработки заданных действий и изменения состояния задач.
import { createReducer, on } from '@ngrx/store';
import { addTask, toggleTask, removeTask } from './task.actions';
import { Task } from './task.model';
export interface TaskState {
tasks: Task[];
}
export const initialState: TaskState = {
tasks: []
};
export const taskReducer = createReducer(
initialState,
on(addTask, (state, { task }) => ({
...state,
tasks: [...state.tasks, task]
})),
on(toggleTask, (state, { id }) => ({
...state,
tasks: state.tasks.map(task => task.id === id ? { ...task, completed: !task.completed } : task)
})),
on(removeTask, (state, { id }) => ({
...state,
tasks: state.tasks.filter(task => task.id !== id)
}))
);
Подключение редьюсера к Store
В корневом модуле или в отдельном модуле состояния импортируем редьюсер:
import { taskReducer } from './store/task.reducer';
@NgModule({
imports: [
StoreModule.forRoot({ tasks: taskReducer })
]
})
export class AppModule { }
Использование состояния и отправка действий в компоненте
В компоненте мы можем подписываться на состояние и отправлять действия для выполнения операций со списком задач.
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Task } from './store/task.model';
import { addTask, toggleTask, removeTask } from './store/task.actions';
@Component({
selector: 'app-task-list',
template: `
- {{ task.title }}
Обработка побочных эффектов с эффектами (Effects)
Для взаимодействия с внешними API и асинхронными операциями в NgRx используются эффекты. Они слушают действия, выполняют асинхронные задачи и могут запускать новые действия на основе результатов.
Например, рассмотрим эффект для загрузки задач с сервера.
Пример эффекта загрузки данных
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { TaskService } from '../services/task.service';
import { loadTasks, loadTasksSuccess, loadTasksFailure } from './task.actions';
import { mergeMap, map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
@Injectable()
export class TaskEffects {
loadTasks$ = createEffect(() => this.actions$.pipe(
ofType(loadTasks),
mergeMap(() => this.taskService.getTasks()
.pipe(
map(tasks => loadTasksSuccess({ tasks })),
catchError(error => of(loadTasksFailure({ error })))
))
));
constructor(
private actions$: Actions,
private taskService: TaskService
) {}
}
В этом примере при действии loadTasks запускается HTTP-запрос, и в зависимости от результата генерируются действия loadTasksSuccess или loadTasksFailure.
Рекомендации по организации и масштабированию проекта с NgRx
NgRx подходит как для небольших, так и для крупных проектов, но для масштабируемости и поддержки стоит соблюдать ряд правил и структурировать код правильно.
Рекомендуется разделять actions, reducers, effects и selectors по функциональным фичам. Это позволит облегчить поддержку и расширение кода, а также упростит тестирование.
Организация файловой структуры
| Папка/Файл | Описание |
|---|---|
| store/ | Основная папка для всего, что связано с NgRx |
| store/tasks/ | Фича-модуль с состоянием задач |
| store/tasks/task.actions.ts | Определения действий (actions) |
| store/tasks/task.reducer.ts | Редьюсер для задач |
| store/tasks/task.effects.ts | Эффекты для задач |
| store/tasks/task.selectors.ts | Селекторы для извлечения данных из состояния |
Использование селекторов
Селекторы позволяют выносить логику получения данных из сложного состояния и повышают повторное использование кода. Например:
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { TaskState } from './task.reducer';
export const selectTaskState = createFeatureSelector('tasks');
export const selectAllTasks = createSelector(
selectTaskState,
(state: TaskState) => state.tasks
);
export const selectCompletedTasks = createSelector(
selectAllTasks,
(tasks) => tasks.filter(task => task.completed)
);
Заключение
NgRx — мощный инструмент для управления состоянием в Angular-приложениях, который значительно упрощает работу с данными, их изменениями и синхронизацией. Использование единого хранилища, действий и редьюсеров обеспечивает предсказуемость поведения и улучшает качество кода. Эффекты помогают эффективно работать с асинхронными задачами, не нарушая архитектурных правил.
При правильной настройке и соблюдении рекомендаций по организации кода NgRx помогает создавать масштабируемые, легко поддерживаемые приложения. Это отличный выбор для проектов, где важна стабильность, прозрачность и тестируемость логики состояния.
Освоив NgRx, разработчики получают мощное средство для построения сложных интерфейсов с удобным контролем за всеми данными и бизнес-логикой.