How I wrote NgRx Store in 63 lines of code
Deep dive into NgRx Store. How I tried to understand it by getting to the very essentials.
Why I love NgRx Store
I was introduced to a concept of Redux state management back in 2017 when I started working on a new Angular 2 project. I have previously worked on an AngularJS application for a year or so only to witness the project not see a day of light.
It failed because developers on the team including myself were constantly over-engineering most of the features which could have been shipped much earlier. This slowed down release significantly and eventually failed the project all together.
There were many problems with that code most of which were contributed from the AngularJS framework design. One of them was state management. The application state was scattered across many singleton services. Those services were injected God knows in how many components which were mutating the state left and right. Oh boy, it was a mess!
I have to say my initial experience with Redux philosophy was not an easy ride. Simultaneously I was learning Typescript, RxJS and NgRx Store. It was fun but not without a struggle. I wanted to pull my hair off while trying to grasp the idea of Observables written in Typescript used in NgRx. But I liked what I saw and what I was learning. I had a good feeling about the whole thing. The pieces of the puzzle were coming together.
Since then I became a passionate proponent of Redux and NgRx Store. Its architecture resonated with me on so many levels. I’ve always been mesmerized with how easy it was to use it and yet I have never really bothered to dig deep enough into its source code to learn how it is built. Special thanks to Alex Okrushko for adding all the sophisticated Typescript Generics which scared me off every time I dared to open the pandora box.
Now it is a time to finally get to the bottom of it.
Let’s get it coded
I have used a super simple Angular NgRx-Seed app with a single reducer and action which I was dispatching on the button click — all I wanted to see was how dispatch was calling the reducer and then a particular selector was aware of the state change. Well, no wonder it is all RxJS magic in action — only this time I was a magician who knew the trick. Now I want to share it with you, my patient reader.
Here is a StoreService I wrote which resembles the NgRx Store but is vastly oversimplified. It covers the basic functionality of dispatching actions, accumulating state and subscribing to updates using selectors. It is coded for illustrative purposes and by no means is intended to be a substitute for a state management library. At least not in its current form.
Let’s take a look at the constructor:
Most of this code is borrowed from state.ts
At first, what is inside the constructor may seem like a lot. Let’s examine what is going on here. You can notice a few BehaviorSubjects, a queueScheduler and a scan operator which is the heart of NgRx Store. I almost missed it at first but Alexander Poshtaruk drew my attention to the importance of the Scheduler. This article helped me to understand why we need it and what it does precisely.
So how does Rx.js QueueScheduler actually work?
And how it can help me to prevent re-entry bug (which causes stack-overflow) + implement breadth-first approach in…
Basically, it makes sure that dispatched actions are received synchronously i.e. in the order they were initiated. If not used it could potentially introduce a re-entry bug — a situation which causes stack-overflow.
After that actions$ is combined with reducer$ using withLatestFrom. I believe we need to be aware of reducer$ changes because it could be changed dynamically by calling ReducerManager#addReducer. I personally never needed this in my application but I decided to keep it in my StoreService implementation just in case.
It is worth nothing as Tim Deschryver pointed out addReducer method will be also invoked if StoreModule.forFeature is used.
ReducerFactory — connecting the dots
I should also mention reducerFactory. I find this piece the hardest to follow in the NgRx Store source. It feels like an endless chain of closures when one tries to debug it.
It all starts with createReducerFactory in store_module.ts.
In my implementation I am omitting the meta reducers handling for the sake of simplicity.
It will use combineReducers as a default factory if config does not provide a custom one. This factory will then be invoked in reducer_manager.ts. Factory will return a reducer combination function which finally is set to be a source of ReducerManager(which is a BehaviorSubject too) and then injected as ReducerObservable in state.ts.
Here is the reducerFactory I am using — nothing more than a function with a closure which composes the nextState iteratively calling every available application reducer:
Scan operator — the heart and soul of the NgRx Store
Now let’s examine what happens after this mixture of actions$ and reducer$ in a form of withLatestReducer$ piped with a scan operator. This is the integral part of NgRx library. It helps to maintain the internal state of the stream. It has two parameters — accumulator function and a seed value. The accumulator has two parameters also — previous state which gets accumulated each time stream is updated and new state value.
My accumulator is a simple reduceState function. This is where application state gets updated and accumulated on every action dispatch and/or reducer update.
Type Unsafe select and createSelector
For the full picture I had to implement select and createSelector functions. But without type safety. And by full picture I mean only my own experience with NgRx and how I use it. I am sure there are a lot of things it can do which my StoreService is not capable of. So here is the select function implementation:
It expects a selector as a single param which later in a selectOperator closure will be invoked in a map operator of the state$ Observable which is named mapped$.
At last this is the createSelector implementation which I kept in reducers’ index.ts file:
I stripped out all the memoization logic from the original source as it is too lengthy and easily can be a subject to another article. Here it is simply separating selectors from a single projector which eventually receives product of all selectors applications.
StoreService Usage in a counter test application
This is how I am using my store in the app — injecting it in app.component.ts. Then I am selecting the counter in ngOnInit which creates an Observable to be passed in the template along with the async pipe. This way of selecting values from the store differs from the original NgRx implementation. Since I don’t have a Store Observable I subscribe to state$ instead which is returned by invoking the getState method.
There are also methods for dispatching the action on increment, decrement and reset buttons’ clicks. All this is pretty standard and aligns with NgRx Store APIs.
Source Code is on StackBlitz 🧐
Enormous thanks to the following folks for reviewing this article:
For those who are still here with me — thank you for reading it all. I tried to make it easy to follow to the best I could. In case there are any comments or questions feel free to add them here or DM me on twitter @e_fedorenko.