Matthew de Nobrega
Matthew de Nobrega in Technology
December 29, 2015

Using immutable data with Angular2

Angular2 is written in Typescript, and most of the plumbing is done with RxJS. While it’s optional to use these to write Angular2 applications, there are significant advantages to using both, and they work seamlessly with the framework. The other bright-and-shiny new technique that is also getting fair amount of mentions within the Angular2 space is immutable data. While Angular2 can leverage immutable data for performance improvements, and using immutable data arguably makes it easier to reason about how changes propagate through your app, switching to immutable data requires a few approach adjustments and involves a few gotchas.

I’ll be using Facebook’s excellent immutable.js for the examples in this article.

Type definitions

Immutable.js offers immutable versions of the standard Collections — Maps, Lists, Sets, etc — but doesn’t deal with typed data as such. It is however possible to use a combination of interfaces and immutable Records to type immutable data (this is taken from a sketch by Viktor Savkin):

import {Record, Map} from 'immutable'

interface Person { name: string }

interface PersonImmutable extends Person, Map {}

let personDefault: Person = { name: '' }

let PersonRecord = Record(personDefault)

let examplePerson: PersonImmutable = new PersonRecord({name: 'test'})

examplePerson.name === 'test' //true

There is a fair amount of overhead to setting up the boilerplate, but once this is done you can use properly-typed immutable data — with the associated tooling benefits.

Forms

Angular2 comes with powerful tools for building forms — but unfortunately they do not work out-the-box if you form data is immutable. The following:

<input [(ngModel)]="person.name"/>

Will throw ‘Cannot set on an immutable record’ when you type into the input if person is an immutable Record. If you need the form to update dynamically when the model changes you are going to have to do some plumbing. If however you only retrieve your data once, you can avoid ngModel and just create controls:

<form [ngFormModel]="form" (ngSubmit)=”onSubmit(form.value)”>
  <div>
    <label for=”name”>Name</label>
    <input type=”text” id=”name” ngControl=”name”>
  </div>
</form>

Then in your component, after you have populated the required form data:

constructor(formBuilder: FormBuilder) {
//populate this.person
this.form = formBuilder.group({‘name’: this.person.name});
}

Nested properties

When converting collections, immutable.js by default only goes one level deep:

import {Map} from 'immutable'

let personWithAddress = {
  address: {
    street: '1 Test street'
  }
}
let pImmutable = Map(personWithAddress)
pImmutable.get('address')['street'] = '2Test street'
pImmutable.get('address')['street'] === '2Test street' // true

In the example below, the value of pImmutable’s address has changed, but pImmutable still points to the original object. It’s possible to use immutable.js’ fromJS function, but this doesn’t generate Records so for proper typing you need to do the nested conversion manually.

Magic strings, generated forms and logging

Three smaller gotchas / inconveniences with immutable data:

  • You can’t use . notation to set a property, which means setters always use magic strings (there’s an example here):

this.person = this.person.set('name', nameFromInput)

  • If you template a form with an ngFor, any changes to the model — when text is entered into an input — will trigger recreation of the DOM, which will take focus off the input.
  • Immutable records are structured differently from vanilla Javascript objects, and while they contain the same data it is more time consuming to debug them when they are logged. In the previous example, if you write pImmutable to the console, you will get something like:

Object {size: 1, _root: ArrayMapNode, __ownerID: undefined, __hash: undefined, __altered: false}

Takeaway

It’s possible and performant to use immutable data for Angular2 apps, but setup and maintenance is non-trivial, and you have to do without (or custom code) some key framework functionality. Luckily Angular2 allows you to specify individual components that rely on immutable data, and as such I think it makes sense to use immutable data in small doses for performance critical components, rather than defaulting to immutable data for an entire application.



0
Matthew de Nobrega
Matthew de Nobrega
Product Owner & Front-end Lead at Lobster Ink