Immutable state in NGXS - Part I.

Ivanov Maxim
NGXS
Published in
5 min readJun 8, 2019

Introduction

Hello everyone, my name is Maxim Ivanov. I am a member of the NGXS team. In the first post of this series I will give a basic overview of object mutability, the problems it causes, and some approaches we can take to prevent the accidental mutation of objects. In the following posts, I will identify the new challenges we encounter in this regard and give recommendations on how to solve them.

NGXS is a state management pattern + library for Angular. Just like Redux and NgRx it is modeled after the CQRS pattern. NGXS uses TypeScript functionality to its fullest extent and because of this it will feel more familiar for Angular developers. Unlike NgRx, NGXS doesn’t use reducers but takes a different approach that leverages decorators and dependency injection to reduce the amount of ceremony needed to modify the state and manage side effects.

What is mutability?

An object is a collection of properties and a property is an association between a key (or name) and a value. A value that can be changed is known as a mutable value; conversely a value that cannot be changed is immutable. In JavaScript, only objects and arrays are mutable. Primitive values are immutable. A mutable object is an object whose state can be modified after it is created.

So, we can further note that baz.foo === fiz.foo. In this code it is likely that you only wanted to change the value of baz.foo and not fiz.foo. So, what is the problem here?

  • Constants are block-scoped much like variables defined using the let keyword. The value of a constant cannot be changed through reassignment and it can’t be redeclared. Unfortunately, the name of the const keyword might be misleading. In JavaScript, const does not mean constant but rather one-time assignment. The part that’s constant is the reference to an object stored within the constant variable, not the object itself;
  • Declaring a variable to be constant doesn’t make the object that it references to be immutable (unchangeable).

Object properties can be changed or can be deleted altogether. The same goes for arrays. Array elements can be added, removed, reordered, or modified. Here is a list of array mutator methods on MDN: copyWithin, fill, pop, push, reverse, shift, sort, splice, unshift

In JavaScript (and many other languages) an assign statement works in two ways:

  • For primitive types (string, number, bool) assignment is passing the value
  • For complex types (objects) it is passing the reference (memory pointer);

As a result, for complex types any change (mutation) to a property will impact all occurrences because they really are only pointers to the same place.

Only primitive types are immutable and can be safely changed, everything beyond primitive types is mutable by default.

The dangers of mutation

As can be seen in the simple example above, mutations to an object can have unintended consequences. These are often referred to as side effects. For example in the following code:

You would expect that this would output “Hello world!” but if the checkLanguage function was defined as follows then the output would be “Goodbye world!”:

Although this is a contrived example this type of code is common albeit in a much less obvious way. The checkLanguage function has side effects on the parameter passed into it. In an object-oriented world, this does not seem so bad. Maybe a code review would have asked the developer to make the mutation more obvious by naming the function more appropriately. In a functional world, this code is just plain evil and that function would be referred to as an impure function. To take a more functional approach to this code we could have the following:

Here the possibility of changes coming from the checkLanguage function is obvious. The function is now pure because it does not have any side effects on state that it does not own. The code is easier to reason about because the mutations are localized an do not cross boundaries.

Let’s look at some strategies to protect us from the perils of mutation…

Preventing objects from mutating

In order to prevent side effects and to track that our objects are mutated we can use different strategies:

TSLint rules to disable mutation in TypeScript (compile-time check)

TSLint warnings

While this is useful, it will often conflict with the paradigms leveraged in Angular.

Freeze object (runtime check)

The Object.freeze() method freezes the object. A frozen object can no longer be changed. Freezing the object does the following:

  • It prevents new properties from being added to it
  • It prevents properties from being removed from it
  • It prevents the values of existing properties from being changed
  • It prevents changes to the enumerability, configurability, or writability of existing properties
  • It also prevents its prototype from being changed.

Object.freeze()returns the same object that was passed for freeze.

If you want to freeze an entire object hierarchy then this requires a traversal of the object tree and for the Object.freeze operation to be applied to all objects. This is commonly known as deep freezing. When the developmentMode option is enabled in the NGXS forRoot module configuration a deep freeze operation is applied to any new objects in the state tree to help prevent side effects. Since this imposes some performance costs it is only enabled in development mode.

Development mode in NGXS

Immutable object wrappers (compile-time check)

The best way to prevent mutations

By leveraging the power of TypeScript’s type system we can opt-in to early warning of mutations where it matters most.

In Summary

  • Mutations to objects or arrays can make code difficult to reason about;
  • Use TSLint rules or immutable wrappers to prevent mutation in objects;
  • Use developmentMode in NGXS to prevent state mutations at runtime.

These strategies only help to prevent mutations of your state but do not help to make immutable objects for you. In the next article, we will learn how to create immutable objects.

--

--

Ivanov Maxim
NGXS
Writer for

Code 🤖, Bike 🚵 and Music 🎶 Teams: @splincodewd ★ @Angular-RU ★ @ngxs ★ github.com/splincode