Simple state mutations in NGXS with Immer
Recently I wrote Clean NgRx reducers using Immer and Austin hinted that I also should write one for NGXS. Of course I couldn’t let him down, so here we are!
NGXS is a state management pattern + library for Angular. Just like Redux and NgRx it’s modeled after the CQRS pattern. NGXS uses TypeScript functionality to its fullest extent and because of this it may feel more Angular-y.
Unlike NgRx, NGXS isn’t using reducers, but it relies on decorators and dependency injection to modify the state. More info and examples can be found below.
In this post we’re not going to see every part of the NGXS API, just the way it handles state mutations. If you’re interested in getting to know NGXS in detail, there are some resources at the bottom of this post.
Also, NGXS just hit 1000 starts on GitHub!
Why Immer link
Immer can simplify the way we edit state.
The thing I like most is that it can just be plugged in wherever needed. Introducing Immer doesn’t mean you have to use it in everywhere in your code base.
About Immer link
Immer (German for: always) is a tiny package that allows you to work with immutable state in a more convenient way. It is based on the copy-on-write mechanism.
It is created by Michel Weststrate, the owner of MobX.
Side by side comparison link
It’s time for some code samples! Like my previous post, we’re going to use the classic shopping cart example where we can add and remove items from the cart.
The cart model can be presented as followed:
To edit the state we have to define 3 actions. Just like with NgRx this can be done by using classes.
Now that we have everything in place we can implement our actions.
As we zoom into the
addProduct method we can see that an action is implemented by using the
Action decorator, and it has a
StateContext parameter and our
AddToCart action. Using the
StateContext we can retrieve the current
CartStateModel slice of our application by using the
getState() method. To edit the state we have to use
setState(T) from the
StateContext, which as the name gives away, sets the new state. NGXS does also have the functionality to modify a part of our state by using
patchState(Partial<T>). An example can be found inside the
removeProduct implementation, where we’re only modifying the
cartItems inside our
If we would use Immer to modify our state, the actions implementation would become: (No other changes are necessary, the rest of the code remains the same!)
The difference here is that we’re mutating the state directly, well to be honest not directly… Because we’re actually mutating the Immer draft from the
produce method, which is the current state of our
StateContext. Every change on
draft will be used to produce the next state, which just like before is being set with
setState. Because the
draft is our ‘full’ state, it doesn’t make sense to use
patchState anymore, thus making this method obsolete.
Notice how easy it is to increment the current amount with Immer.
To give another example, let’s take a look at how we load the catalog:
Pretty straight forward, right?
Transitioning to Immer link
You can safely start using Immer in some parts of your application. Using Immer doesn’t mean a big bang migration, but it can be implemented where needed and this can be done step by step.
Just like I said in my previous post, by using immer you won’t lose any benefit of NGXS or NgRx. This means:
- selectors will still be memoized
- the redux devtools keeps working
BUT be aware that Immer comes with object freezing out of the box in development. This means that it will throw an error if the state is mutated from outside the
produce function. If you want to mutate your state (which I don’t recommend by the way), you can turn off this feature with
setAutoFreeze(false). If the application is built in production mode, this check will automatically be skipped for performance reasons.
Great, but how can I use it link
In order to use Immer you’ll have to install it first via
npm install immer, and then import it with
import produce from 'immer’.
My conclusion is a bit different as in Clean NgRx reducers using Immer.
I still find Immer a great library, but in contrast to NgRx I think I would quicker use Immer with NGXS. Because to me it goes hand in hand with the NGXS mindset.
I also think it would be even more handy if we could do the following
This post is meant to be a short introduction to Immer and to spread the word to the NGXS community (and also to score some points with Austin of course 😃). If you like what you’re seeing and want some more details or if you’re interested on how Immer works, there are some useful resources below.
The code from the cart example can be found on GitHub or directly on StackBlitz.
More resources link
- Introduction - NGXS
- Why another state management framework for Angular?
- Introducing Immer: Immutability the easy way
- The Immer repository on GitHub
I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.