Why I Prefer NGXS over NGRX
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
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 Reducers, Effects, Selectors
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:
- @ngrx/store — RxJS powered state management for Angular applications, inspired by Redux
- @ngrx/effects — Side Effect model for @ngrx/store to model event sources as actions.
- @ngrx/router-store — Bindings to connect the Angular Router to @ngrx/store
- @ngrx/store-devtools — Store instrumentation that enables a powerful time-traveling debugger.
- @ngrx/entity — Entity State adapter for managing record collections.
- @ngrx/schematics — Scaffolding library for Angular applications using NgRx.
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
localStorage
,sessionStorage
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.