Photo by Tim Arterbury on Unsplash

Previously proposed and blogged here as Patch Operators, this idea has gone through a number of iterations, changed names and now its’ time has finally come. I’m pleased to announce that State Operators will be arriving in NGXS 3.4!

So, what is this all about and why are we making a fuss of it?

The Problem

One of the common challenges with writing code for Redux frameworks is the fact that the state is immutable. When you want to change your state you need to make changes to your own copy of the state and then supply that as the new state. You will commonly find code like this.

Here is an example from an application using NGXS:

This code has all the smells of the typical immutable updates that you will find in most state management code.

This looks like a lot of boilerplate to me! This is actually a cleaner version because it leverages the spread operator as opposed to the more traditional alternatives where we would be using things like `Object.assign`, `Array.slice` and `Array.splice` to achieve these copy-on-write type operations for objects and arrays. Given the prevalence of these patterns there is an Immutable Update Patterns page that describes these patterns for redux (https://redux.js.org/recipes/structuring-reducers/immutable-update-patterns).

Due to the smells and repetition present in this code there are many libraries that have been developed to help in this regard (48 listed here https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities last time I checked). This becomes yet another decision the developer needs to make for their redux stack. We aim to make this decision a bit simpler.

Enter State Operators…

A State Operator is simply a function that, given a state value, will return a new state value with some immutability update applied.

In NGXS 3.4 we have included support in stateContext.SetState for the developer to provide a state operator function as opposed to the new state value. This function will be called with the current state as its argument and it will return the value to be used as the new state. The signature for a State Operator function is as follows: <T>(existing: Readonly<T>) => T

The simple loading property update in the loadCities part of the code snippet above could be transformed as follows:

Let’s look at these refactorings, one at a time:

Refactoring 1 - use the operator syntax

We no longer need to call getState to retrieve the current state. This also prevents the temptation to use this value inside the tap where it may now be outdated. Sound good. Now, let’s look at the next refactoring:

Refactoring 2 - extract the state operator code into a function

By extracting a function we are able to move the copy-on-write logic out of our code. Another win! Now let’s take it one step further:

Refactoring 3 - extract a more domain specific concept State Operator

We create a specially named State Operator for when we start loading called startLoading . No rocket science needed to tell what that does. Now we are able to express our code in a beautifully declarative manner:

setState(startLoading());

This State Operator could be used anywhere in your application that you need to do the same sort of update. But hang on, what if you call the above code and your loading property is already set to true? We would be returning a new version of the state when absolutely nothing has changed!

This is often an issue with the hand rolled version of immutability updates. We are already writing so much extra code to make the update that we often don’t consider when we don’t need to update. Now that we have a single shared function representing the update we can get a bit smarter here. Let’s improve the setLoading State Operator:

PS. These functions are pure and therefore very easily unit testable too!

We have now added some logic to handle the case where no changes are necessary and have also added some type safety so that this operator can only be used on states that have a loading property.

Further refactoring could be applied to the loadCities handler and we could eventually end up with code that looks like this:

Here we have also used the `compose` State Operator provided in NGXS which you will see listed below.

Now that’s an improvement!

“So, if State Operators allow us to create more declarative updates and to share common update patterns within our application… are there any patterns common to most applications?”

I’m glad you asked!

Provided State Operators

We have included a number of useful State Operators in NGXS:

patch , updateItem , removeItem , insertItem , append , compose , iif

These operators will cover your general cases for updating objects and arrays, and combining State Operators as needed.

Note that all of these operators are coming with @ngxs/store v3.4 in a sub-package called @ngxs/store/operators . They are fully tree-shakeable.

The goal of these operators is to provide a declarative way to express your immutable updates in a form that is type safe and is supported by typescript language intellisense.

Read the docs here for more information:

https://ngxs.gitbook.io/ngxs/v/master/advanced/operators

Leveraging the standard NGXS State Operators above we can refactor the addCity action handler from the orginal example in the following way:

Now, let’s look at these refactorings:

Refactoring 1 - leverage the provided state operators

Maybe a few more concepts going on here because this is the first time you are seeing these provided State Operators being used but let’s look at this…

We are using patch to make changes to the CitiesStateModel, and we are specifying operations to apply to two of its’ properties entities and ids . Because entities is an object being used as a hash map we use patch again to modify the object and add (or update) the new property. Then we are updating the ids array by appending the new id at the end. Pretty straightforward to understand. Note the declarative nature of this code. No more imperative style code creating noise in this update. Now, the most important thing of all… this code takes all immutability concerns into consideration and is fully type safe with code completion support!

Your application will most likely do a similar sort of update in many places, so we could create out own State Operator that encapsulates these details. So, let’s look at the next refactoring:

Refactoring 2 - extract a general purpose addEntity State Operator

Here we introduce a basic interface to represent this shape of state model. We then generalise the code we had in our action handler to be more general purpose. The addEntity State Operator that we have created is explicit about its’ expections of the shape of the entity it can add and the shape of the state to which it will be adding the entity. This general purpose State Operator can now be used throughout our application to create the following expressive form of update:

ctx.setState( addEntity(city) );

Now that’s someting to get excited about!

NB Note: After NGXS 3.4 is released we will be developing a number of these entity State Operators as part of the Entity-State NGXS Labs project: https://github.com/ngxs-labs/entity-state

Using other Immutable Update libraries

As an aside, many of the redux immutable update utility libraries mentioned before would also actually be compatible with the State Operator function form in one way or another.

Most notably the curried form of the produce function from the immer library (see npm and github) has a valid State Operator signature. This could be used in the loading property update or the addCity function as follows:

Some people prefer to express their updates as if the state was mutable. For them this would be a great way to go. Also note that there is nothing stopping you from using these third party libraries to perform your immutibility updates within operators that you create because they are just pure methods after all.

Wrapping it up

We hope that you are as excited about the possibilities with State Operators as we are. We would love to find more common scenarios that would find their place in the State Operators package (or a labs package) to further reduce the boilerplate in your code and make your code even more expressive. Please let us know if you create any operators that you think would add value to the library in any way.

NGXS v3.4 has been released so you can start using these in your app today!!