Использование 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 в корневой модуль приложения, настроив их на работу с редьюсерами и эффектами.

Шаги установки

  1. Установка основных пакетов:
    npm install @ngrx/store @ngrx/effects @ngrx/store-devtools
  2. Импорт StoreModule и регистрация корневого редьюсера:
    import { StoreModule } from '@ngrx/store';
    import { reducers } from './store/reducers';
    
    @NgModule({
      imports: [
        StoreModule.forRoot(reducers)
      ]
    })
    export class AppModule { }
        
  3. Подключение EffectsModule для работы с побочными эффектами:
    import { EffectsModule } from '@ngrx/effects';
    import { UserEffects } from './store/effects/user.effects';
    
    @NgModule({
      imports: [
        EffectsModule.forRoot([UserEffects])
      ]
    })
    export class AppModule { }
        
  4. Подключение 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 }}
` }) export class TaskListComponent { tasks$: Observable; newTaskTitle = ''; constructor(private store: Store<{ tasks: { tasks: Task[] } }>) { this.tasks$ = store.select(state => state.tasks.tasks); } add() { const task: Task = { id: Date.now().toString(), title: this.newTaskTitle, completed: false }; this.store.dispatch(addTask({ task })); this.newTaskTitle = ''; } toggle(id: string) { this.store.dispatch(toggleTask({ id })); } remove(id: string) { this.store.dispatch(removeTask({ id })); } }

Обработка побочных эффектов с эффектами (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, разработчики получают мощное средство для построения сложных интерфейсов с удобным контролем за всеми данными и бизнес-логикой.

NgRx в Angular state-менеджмент Angular использование NgRx управление состоянием приложений Angular store для новичков
эффективный state management RxJS и NgRx селекторы в NgRx actions и reducers NgRx примеры NgRx в Angular