My Developer Experience with NGXS
As an Angular developer, I have been working with NgRx for a while. However, recently I keep hearing about this state management NGXS. I heard many good things about this library and one pro of this library is that it has a better “developer experience”. As a developer, I want to try it out to see what kind of experience this library would give me and see whether it could “convert” me.
I have created this sample project using NGXS:
It is a simple web app that allows you to search a superhero by name, race, and gender: https://xiongemi.github.io/superhero-search-engine/.
Before I Started…
I checked npm trends for these 2 libraries. NgRx is still dominant at the time I wrote this blog (March 2020).
There is definitely a bigger community around NgRx than NGXS.
Less Boilerplate?
I read a few blogs online, and most of them mentioned that NGXS has less boilerplate. I was wondering, since both libraries are inspired by Redux, how would one has less boilerplate than the other. When I started using it following the documentation, I finally get what it meant.
So it basically means instead of having 3 files in NGRX (effects, selectors, reducers), it seems to dump everything into one file (state).
NGXS’s .state = NGRX’s .effects + .reducers + .selectors
What I like: .effects + .reducers are together. I could change the state directly inside an asynchronous callback. I don’t need to fire another action in an effect then modify the state in the reducer.
For example, I need to call an API to get all superheroes and put it in the state, in NgRx, I might need 3 actions:
GetSuperheros
to call API.GetSuperherosSuccess
when API returns a valid response and I need to update the state with this new response.GetSuperherosError
when API throws an error and I need to handle this error.
In NGXS, I only need 1 action GetSuperheros
, and the state file would look like this:
@Action(GetSuperheros)
getHeros(ctx: StateContext<Hero[]>) {
return this.herosService.getHeros().pipe(
tap(heros => {
// equivalent to GetSuperherosSuccess
ctx.setState(heros);
}),
catchError(() => {
// equivalent to GetSuperherosError
return ctx.dispatch(new HandleApiError(new GetSuperheros()));
})
);
}
What I don’t like: In the NGXS’s document, it uses @Select
syntax in the components or puts selectors as static methods of state class. I still prefer to put selectors as pure functions in a separate .selectors
file. It is easier for me to unit test.
Selectors
Talking about the selectors, NGXS has a build-in function createSelector
wherein NgRx, I need to install the 3rd-party reselect
library.
NGXS has more built-in plugins than NgRx, such as forms and router.
Forms
- NgRx: ngrx-forms (3rd party)
- NGXS: @ngxs/form-plugin (build-in)
To sync the form values with the state, for NgRx, I used the 3rd party library ngrx-forms before. Here is a blog about how to use this library: https://rangle.io/blog/ngrx-forms/. NGXS has its own built-in plugin.
These 2 libraries work very differently and are hard to compare. NGXS’s form plugin still uses Angular’s reactive form; however, ngrx-forms However, if I have a reactive form that is already working, and I need to sync the value and status with the state, NGXS requires less effort.
Plugins
Below is the plugins/libraries for NgRx vs NGXS. So for all the things I could do in NgRx, I could find something equivalent in NGXS.
Router
- NgRx: @ngrx/router-store (build-in)
- NGXS: @ngxs/router-plugin (build-in)
Devtools
- NgRx: @ngrx/store-devtools (build-in)
- NGXS: @ngxs/devtools-plugin (build-in)
Logger
- NgRx: ngrx-store-logger (3rd party)
- NGXS: @ngxs/logger-plugin (build-in)
Storage
- NgRx: ngrx-store-localstorage (3rd party)
- NGXS: @ngxs/storage-plugin (build-in)
Unit Testing
Testing asynchronous actions in .state files
Since NGXS’s .state file is not made of pure functions, it is a bit tricky to test asynchronous actions. In NGXS’s documentation, there seems to be a lot of setup for unit testing.
I did not follow the documentation that uses Testbed
, but rather I mocked up the StateContext
:
export class MockStateContext {
getState = jest.fn();
patchState = jest.fn();
setState = jest.fn();
dispatch = jest.fn();
}
I used jest
as my unit testing framework. If you use jasmine
, you could use spyOn
to mock up these functions.
Below is an example of a unit test in my sample project. It checks stateContext.setState
to be called with the expected new state:
describe('Heros State', () => {
let herosService: MockHerosService;
let herosState: HerosState;
let stateContext: MockStateContext;beforeEach(() => {
stateContext = new MockStateContext();
herosService = new MockHerosService();
herosState = new HerosState(herosService as any);
});it('should set heros if it does not exist', done => {
const heros = [mockMaleHumanHero, mockOtherNonHumanNeutral];
stateContext.getState.mockReturnValue([]);
herosService.getHeros.mockReturnValue(of(heros));herosState.getHeros(stateContext as any).subscribe(() => {
expect(stateContext.setState).toHaveBeenCalledWith(heros);
done();
});
});
});
I find unit testing with the state files is not that difficult, similar to unit testing the .effects files in NgRx.
For .selectors files, since it is made of pure functions, it is pretty simple to unit tests.
Documentation
NgRx definitely has better documentation. I really like the api page of NgRx: https://ngrx.io/api.
I was trying to find the return type of Store.dispatch
function, in typing file, it says Observable<any>
, but I really want to know what the any
type is. I could not find it in the online documentation, I need to console log, and turns out the return is undefined. I do wish this library has a quick API search documentation.
RxJS?
I have read some blogs mentioned that by using NGXS, developers don’t need to be very familiar with RxJS. For my sample app, I only used operatorstap
and catchError
. However, as an Angular developer, I don’t think we could bypass RxJS. I think developers still need to be familiar with RxJS regardless.
Summary
This is just my personal 2-cent about NGXS as a developer.
Converted? Yes.
2 reasons: forms and fewer actions created.
If a future project requires a lot of form handling or it already uses reactive forms, I would definitely use NGXS.
It also requires fewer actions to be created, so less the code, merrier the developers.
There are always new libraries that come out every year. Even though NGXS is a new library, but it is still “Redux inspired”. As long as developers understand Redux, it is not hard to pick it up.
Happy coding and happy state managing.