Why I Prefer NGXS over NGRX

 In Tech Corner

What’s NGRX and NGXS

NGXS is a state management pattern + library for Angular. It acts as a single source of truth for your application’s state, providing simple rules for predictable state mutations.

NGXS is modeled after the CQRS pattern popularly implemented in libraries like Redux and NGRX but reduces boilerplate by using modern TypeScript features such as classes and decorators.

NGRX is RxJS powered state management for Angular applications, inspired by Redux

They both are state management libraries for Angular application. Both of them share Redux principle. It depends on your preference which one to choose, but I think NGXS is more Angularish and I will try to show you why you should choose it, too.

Actions vs Actions

zoo.actions.ts in NGXS:

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

zoo.actions.ts in 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;

State vs ReducersEffectsSelectors

zoo.state.ts in 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 in 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 in 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))
    );
}

Component vs Component

zoo.component.ts in 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 in 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 }));
  }
}

Tools & Plugins of Each Library:

NGRX has some great additional tools, that will help you manage your store. This is a full list of tools that they provide:

While NGXS gives us the ability to use built-in plugins or write our own. Their list of plugins is:

  • Logger — A simple console log plugin to log actions as they are processed.
  • Devtools — Plugin with integration with the Redux Devtools extension.
  • Storage — Back your stores with localStoragesessionStorage or any other mechanism you wish.
  • Forms — In a nutshell, this plugin helps to keep your forms and state in sync.
  • Web Socket — Bind server websocket events to Ngxs store actions.
  • Router — This plugin binds that state from the Angular router to our NGXS store.

What I love and what I don’t love about NGXS

I really like how you can manage things in NGXS. Me and my team had an intermediate project written in NRGX. I wanted to rewrite state management in NGXS and I did it without any problems. It took me about 2–3 hours, but now I feel more comfortable with my code.

But there are some little things that I lack from NGXS. I think their documentation could be use some improvement. There are some things that I think I like but can’t use because of lack of documentation.

Example: State selector has an ability to read passed argument and return state depending on that argument. But there is no documentation on how to pass that argument from the component.

Despite this, NGXS is a great tool to implement in any of your Angular projects. This will definitely help you manage every step of your state with as little work as possible.

The post was written by Zura Gabievi.

Recommended Posts
Project Management and IT conference automation testing

SINGULAR STORY IN YOUR INBOX

Get news about career opportunities and product updates once a month.