Angular 2.0 introduces a component-based approach to building applications, where the rendering can be optimised by selecting a suitable change detection strategy for each component. This post looks at how the
OnPush change detection strategy works quite elegantly with the concept of immutable objects as enforced by Immutable.js.
In my previous blog post on Angular 2.0 I described how to build the classic ‘todo list’ application as shown below:
The app has two simple Angular components; the first is the top level todo-list, which is responsible for rendering the todo input field, and an array of items, which are themselves rendered using the todo-item component:
Property bindings are used to supply data to each todo-item, and event bindings are used inform the parent todo-list component of any updates, which it delegates to the store.
The previous post showed how the default change detection strategy causes a dirty check for each binding on every keypress and button-click. However as the the todo items are not mutated in the current implementation, The
OnPush change detection strategy can be employed in order to minimise the amount of dirty checking required.
The OnPush strategy will only dirty check a component if its input properties change.
While this improves the rendering performance for the previous example, there are a couple of problems:
- The todo items are not strictly immutable. They are objects with setters as well as getters, and the code could accidentally be updated to break the contract I have with Angular via the
- The todo application is a little simplistic, in reality I’d also like to be able to edit the text of existing todo items rather than just delete them. In other words, I’d like them to be mutable.
How can these two issues be resolved while still benefiting from the OnPush strategy? This blog post takes a closer look …
The first issue I am going to look at is immutability. When using the OnPush change detection strategy you are informing Angular that all the values supplied to a component (via its input properties) are immutable. If you break this contract your application will find itself in an inconsistent and somewhat unpredictable state.
To a generic
Mutation methods now create a new copy of the items rather than mutating them in-place. Here you can see the updated
addItem method from the store:
Making the list immutable is pretty trivial - individual items is harder.
With Immutable.js you can create immutable objects using
Map property access is performed via the
get method, and mutations are performed via the
set method, which returns a new instance with the mutations applied. It’s clear that todo items should be map-like objects, however I don’t want to lose the strong-typing that TypeScript provides.
Immutable also has the concept of a Record which generates property accessors in order to give a more familiar API:
This looks more useful from a TypeScript perspective, although currently it doesn’t look like there is an easy way to make an immutable Record work with interfaces as discussed in this issue.
I instead opted for a manual approach:
The above class forms a very thin wrapper around an immutable Map, with property getters allowing strongly-typed access, and corresponding set methods, which return new todo item instances rather than mutating.
The one other change above is the use of node-uuid to generate unique identifiers for each item. With property setters copying in order to mutate, reference equality no longer makes sense.
Here’s an example of this class in action:
Now that the items are strictly immutable, enforced both at compile-time by TypeScript and runtime by Immutable.js, the use of the OnPush change detection strategy doesn’t feel quite so risky.
However, this introduces another problem, ideally items in the list should be editable.
The TodoMVC project has a specification for app implementations which I am following here.
I’ve updated the todo item component to have two different views, which are shown / hidden based on the
editing class, which is bound to the
editMode component property via the
class.editing binding. All quite standard Angular 2 stuff …
Double clicking the label moves the application into edit mode:
setTimeout ‘hack’ above. I’d like to set focus on the input element when it is shown. However, I can only set focus when the item is visible. The effect of changing the
editMode property will only be reflected in the DOM after the current VM turn (thanks to zone.js). Therefore, in order to execute my focus logic after the DOM has been updated, I use a timeout to push it onto a future VM turn.
I’d hoped that one of the component lifecycle hooks might provide a better solution, although I haven’t found a hook for this logic. Interestingly the React TodoMVC implementation makes use of lifecycle methods in order to update focus.
If anyone has a better solution, I’d be very happy to hear about it!
The todo item component uses an event to inform the parent component of changes, here’s the new event:
And the corresponding interface which describes the update:
When an item is committed either via a keypress or loss of focus, the event is emitted:
Whereas a cancel just reverts the input element to its previous state:
Notice the DOM is responsible for holding the transient state of the todo item. This may or may not be a good thing, I’m still thinking about that one!
The todo list component handles the events raised by the todo item components, delegating to the store to update the state of the app:
Within the store, the update functions (
updateCompletion) find the item based on their uuid, create a new item with the updated state, then update the list:
removeItem method is even simpler:
The above all works very nicely with the OnPush strategy. When an item is edited, the updates are applied via the store and the original todo item is replaced with a new one with the updated state. As a result the
item property of the todo item component is changed (to reference this new object) and as a result the component is re-rendered.
I’ve updated the GitHub repo with these changes. From my perspective, Angular 2 and Immutable.js work really well together. I’m looking forwards to exploring these concepts further!
Regards, Colin E.