Why I didn't just use NgRx, NGXS or Subjects in a Service

Mike Pearson - Apr 7 '21 - - Dev Community

I am about to criticize some beloved state management solutions, and this is bound to upset some people. Great people have worked hard on these, and I am seriously grateful to anyone who has contributed to the many state management options out there. However, I have been asked to clarify why I created StateAdapt instead of using an existing solution.

Before you read this you should read my article introducing StateAdapt. It is much more positive than this.

Anyway, let's get into it.

NgRx

What NgRx is missing

State pattern is coupled to state

Recently I created a complex tree of components in an NgRx project only to find out later that a 2nd copy of that tree of components needed to exist on the same page at the same time. I would need to add an extra property to 30 actions so that the reducer could become more complicated and know whether an action should apply to one place in the state tree or another.

If we managed state like we built UIs—with components—we could add another instance of a state pattern in as little as 2 lines of code. I believe we could achieve this with the state adapter pattern introduced in NgRx/Entity, but I think most people would view it as even more boilerplate unless the surrounding boilerplate was reduced at the same time. That is what I did in StateAdapt.

Boilerplate

Everyone knows about the boilerplate problem with NgRx. The NgRx Core team has done a terrific job of reducing this, but in order to reduce it to a minimum you would need to take full advantage of RxJS, which StateAdapt does. StateAdapt achieves the same benefits as NgRx with 60% the minimum code compared to modern NgRx.

Best practices?

NgRx seems to try to distance itself from Redux, but it is still a copy of Redux wrapped in an observable. Unfortunately, there is a lot of wisdom and best practices in the Redux community that did not successfully make its way over. There is some guidance, like this terrific talk by Mike Ryan, but I believe people are largely using NgRx in a way that minimizes the benefits they get from it.

I wrote Stop using ngrx/effects for that in 2017 in the hopes that people would stop using ngrx/effects for everything. It mostly didn't work. The first example in the NgRx/Effects documentation is for something that NgRx/Effects shouldn't be used for: Subscribing to data by dispatching an action. In my article I explain why using plain RxJS makes data dependencies much more flexible and maintainable.

My solution did not catch on very well, and I have seen indications that people were intimidated by the little-known using function from RxJS that it requires. Unfortunately I was too lazy to write more about that solution. So, one of my primary goals in StateAdapt was to just take care of that behind the scenes. I am very happy with how it turned out. With StateAdapt, developers can focus on thinking reactively and not worry about how to involve the store.

NGXS

What NGXS is missing

State pattern is coupled to state

This is the same problem I talked about with NgRx above.

Everything is an effect

I already thought people were drastically overusing NgRx/Effects, but then somebody created a state management library where literally everything is an effect.

Rather than repeat myself about why I love pure functions, I will just refer you to my first article. I think it is wonderful that people are trying to reduce state management boilerplate, but I personally find NGXS code to be hard to understand. Data structure manipulation gets in the way of trying to understand the overall data flow in an application, and vice versa. I like separation of concerns between the big picture stuff and the details of how state is changed. NgRx has that (when not overusing NgRx/Effects) but it comes at the cost of all that boilerplate. But StateAdapt maintains it while reducing boilerplate.

Multiple action dispatching

Dispatching multiple actions at the same time is an anti-pattern in Redux, and NGXS encourages it with special syntax.

One of my first experiences in an NGXS application was opening Redux Devtools and seeing this:

Multiple Actions Dispatched

I looked at the code and saw that these actions were all dispatched at the same time.

The line between what was happening and how the app reacted was blurred to the point where I could no longer clearly see what was happening. There should have just been one action dispatched: AnswerCall. If I was interested in the details, I could click on it and look at the state changes.

Also, if you try to jump to any of these actions in Redux Devtools it will put the app in an intermediate state that is impossible in reality.

The last issue is that this is backwards from FRP. Rather than multiple action handlers listening to one action in their respective state files, the event source is in charge of making all the changes downstream. An event source that dispatches multiple actions is a lot more than an event source.

Akita

What Akita is missing

Imperative State Management

State is updated imperatively in Akita, like it tends to be in NGXS. Actually, NGXS would be improved if its syntax were more like Akita's, but they both suffer from the issues I mentioned in the section about NGXS.

Subjects in a Service

What RxJS is missing

No Redux Devtools

RxJS is notoriously annoying to debug. Usually you have to edit the file by putting a tap(console.log) in there, reload the app and reproduce the situation you wanted to debug. But Redux Devtools keeps track of everything automatically, so you can just open it anytime and explore what happened at any time.

(Actions and state reactions are a big part of understanding what is happening in an application, so Redux Devtools is great, but I am still trying to find a better way to debug RxJS for the other parts of applications. If anyone knows of anything, let me know.)

No Selectors

Sometimes you want to combine observables, but combineLatest emits once for each input observable, even if the input observables emit synchronously. I believe this is the original reason NgRx included createSelector, because selectors solve this.

It is also really nice to have all the derived state calculated in pure functions, separate from the asynchronous RxJS stuff.

Another benefit is with derived state. Pure RxJS makes you map, distinctUntilChanged and shareReplay if you want to calculate derived state efficiently. Selectors do not need any of that.

StateAdapt

StateAdapt has everything
So, all of that is why I wrote StateAdapt. Is it perfect? No. Is it for everyone? Well, I think it is for everyone who loves minimal, reactive, debuggable and reusable code.

Give it a try and let me know what you think.

Others

If there are any I missed, please let me know. I searched a lot before I wrote StateAdapt, but I may have missed something.

Also, if anyone wants to see any comparisons, I would love to create some. I already plan on doing some. But if you show me a feature developed in one state pattern, I will recreate it using StateAdapt.

Thank you!

And please forgive me! I love you all!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .