Что такое NGRX и NGXS

NGXS - это шаблон управления состоянием + библиотека для Angular. Он действует как единый источник истинности состояния вашего приложения, предоставляя простые правила для предсказуемых мутаций состояния.

NGXS смоделирован на основе шаблона CQRS, широко используемого в библиотеках, таких как Redux и NGRX, но сокращает количество шаблонов за счет использования современных функций TypeScript, таких как классы и декораторы.

NGRX - это управление состоянием на базе RxJS для приложений Angular, вдохновленное Redux.

Оба они являются библиотеками управления состоянием для приложения Angular. Оба они разделяют принцип Redux. Выбор зависит от ваших предпочтений, но я думаю, что NGXS более Angularish, и я также постараюсь показать вам, почему вы должны выбрать его.

Действия vs Действия

zoo.actions.ts в NGXS:

export class FeedZebra {
  static readonly type = '[Zoo] Feed Zebra';
  constructor(public name: string, public hayAmount: number) {}
}

zoo.actions.ts в NGRX:

import { Action } from '@ngrx/store';
export const FEED_ZEBRA = '[Zoo] Feed Zebra';
export class FeedZebra extends Action {
  readonly type = FEED_ZEBRA;
  constructor(public payload: { name: string, hayAmount: number }) {}
}
export type Actions = FeedZebra;

Состояние vs Редукторы, Эффекты, Селекторы

zoo.state.ts в NGXS:

import { State, Selector, Action, StateContext } from '@ngxs/store';
import { ZooService } from '../services/zoo.service';
export interface ZooStateModel {
  zebraName: string;
  zebraFeed: boolean;
}
@State<ZooStateModel>({
  name: 'zoo',
  default: {
    zebraName: null,
    zebraFeed: false
  }
})
export class ZooState {
  constructor(private zooService: ZooService) {}
  @Selector()
  static zebraName(state: ZooStateModel) {
    return state.zebraName;
  }
  @Selector()
  static zebraFeed(state: ZooStateModel) {
    return state.zebraFeed;
  }
  @Action(FeedZebra)
  feedZebra(ctx: StateContext<ZooStateModel>, action: FeedZebra) {
    return this.zooService.feedZebra(action.zebraName, action.hayAmount).pipe(
      tap(() => ctx.setState({ zebraName: action.zebraName, zebraFeed: true }))
    );
  }
}

zoo.reducer.ts в NGRX:

import { createFeatureSelector, createSelector } from '@ngrx/store';
import * as fromActions from '../actions/zoo.actions';
export interface State {
  zebraName: string;
  zebraFeed: boolean;
}
export const initialState: State = {
  zebraName: null,
  zebraFeed: false
};
export function reducer(state: State = initialState,  action: fromActions.Actions): State {
  switch(action.type) {
    case fromActions.FEED_ZEBRA:
      return {
        zebraName: action.payload.name,
        zebraFeed: true
      };
  }
  return state;
}
export const getZooState = createFeatureSelector<State>('zoo');
export const getZebraName = createSelector(getZooState, state => state.zebraName);
export const getZebraFeed = createSelector(getZooState, state => state.zebraFeed);

zoo.effects.ts в NGRX:

import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { Observable } from 'rxjs';
import { ZooService } from '../services/zoo.service';
import * as fromActions from '../actions/zoo.actions';
@Injectable()
export class ZooEffects {
  constructor(
    private actions$: Actions,
    private zooService: ZooService
  ) {}
  @Effect()
  feedZebra$: Observable<Action> = this.actions$
    .ofType<fromActions.FeedZebra>(fromActions.FEED_ZEBRA)
    .pipe(
      map((action: fromActions.FeedZebra) => action.payload),
      switchMap(payload => this.zooService.feedZebra(payload.zebraName, payload.hayAmount))
    );
}

Компонент vs Компонент

zoo.component.ts в NGXS:

import { Select, Store } from '@ngxs/store';
import { ZooState } from '../zoo.state';
import { FeedZebra } from '../zoo.actions';
import { Observable } from 'rxjs';
@Component({ ... })
export class ZooComponent {
  @Select(ZooState.zebraName)zebraName$: Observable<string>;
  @Select(ZooState.zebraFeed)zebraFeed$: Observable<boolean>;
  constructor(private store: Store) {}
  feedZebra(name, hayAmount) {
    this.store.dispatch(new FeedZebra(name, hayAmount));
  }
}

zoo.component.ts в NGRX:

import { Store } from '@ngrx/store';
import { ZooState } from '../zoo.state';
import { FeedZebra } from '../zoo.actions';
import * as fromZoo from '../zoo.reducer';
import { Observable } from 'rxjs';
@Component({ ... })
export class ZooComponent {
  zebraName$: Observable<string>;
  zebraFeed$: Observable<boolean>;
  constructor(private store: Store<fromZoo.State>) {
    this.zebraName$ = this.store.select(fromZoo.getZebraName);
    this.zebraFeed$ = this.store.select(fromZoo.getZebraFeed);
  }
  feedZebra(name, hayAmount) {
    this.store.dispatch(new FeedZebra({ name, hayAmount }));
  }
}

Инструменты и плагины каждой библиотеки:

NGRX имеет несколько отличных дополнительных инструментов, которые помогут вам управлять своим магазином. Это полный список инструментов, которые они предоставляют:

А NGXS дает нам возможность использовать встроенные плагины или писать свои собственные. Их список плагинов:

  • Logger - простой плагин консольного журнала для регистрации действий по мере их обработки.
  • Devtools - Плагин с интеграцией с расширением Redux Devtools.
  • Хранилище - поддержите ваши магазины с помощью localStorage, sessionStorage или любого другого механизма, который вы пожелаете.
  • Forms - вкратце, этот плагин помогает синхронизировать ваши формы и состояние.
  • Веб-сокет - привязать события веб-сокета сервера к действиям хранилища Ngxs.
  • Маршрутизатор - этот плагин связывает это состояние от маршрутизатора Angular с нашим хранилищем NGXS.

Что мне нравится и что мне не нравится в NGXS

Мне очень нравится, как вы можете управлять вещами в NGXS. У меня и моей команды был промежуточный проект, написанный на NGRX. Я хотел переписать управление состоянием в NGXS и сделал это без проблем. На это у меня ушло около 2–3 часов, но теперь я чувствую себя более комфортно со своим кодом.

Но есть некоторые мелочи, которых мне не хватает в NGXS. Я думаю, что их документацию можно было бы улучшить. Есть некоторые вещи, которые мне нравятся, но я не могу использовать их из-за отсутствия документации.

Пример: селектор состояния может читать переданный аргумент и возвращать состояние в зависимости от этого аргумента. Но нет документации о том, как передать этот аргумент из компонента.

Несмотря на это, NGXS - отличный инструмент для реализации в любом из ваших проектов Angular. Это определенно поможет вам управлять каждым шагом вашего состояния с минимальными затратами усилий.