Horrified! That seemed to be the collective reaction when the Angular team announced two-way data binding would not be directly present in Angular2. Since then, the team have worked hard to reassure the community.
However, one-way data flows are attracting ever more devotees. This pattern has been popularised by React / Redux, and is epitomised by Elm. One-way binding, coupled with a single (immutable) source of state truth, is increasingly seen as a route to faster, hot reloadable, testable code, while enabling stunning (time-travelling) debug tools.
What would a one-way, single state pattern look like in Angular2? This post seeks to emulate the basic patterns in the first four examples from The Elm Architecture.
The basic pattern
Throughout these examples, we will keep state in a parent component. The parent will inform the children of the state they need to render, and the children will send action events back to the parent to cause state updates. After each update, the parent will cause the children to re-render with new values. All the code is in Typescript and can be found here, and see the different branches corresponding to each example and an updated to Angular2-beta of the final Redux version is here.
Example 1: A counter
To start we will implement a single, simple counting component that shows its currently value and which can be increased or decreased, as shown in the image.
Let’s start with the ‘View’ template, which simply attaches a click listener to each button using the new Angular syntax.
Now let’s look at the component’s Javascript.
There are several things to note here:
- the CounterComponent class has no local state!
- The component receives, as an
@Input
, its count value, which is rendered directly by the view; and - data leaves the component via the
@Output
function in the form of an event. In this case we emit (via thenext
method) what the state should be updated to.
The CounterComponent is instantiated by the parent App component.
Here we see:
- the model, which is a single number for the time being;
- the template that passes ‘downward’ the value in the model, and which providers the
updater
function to receive ‘events’ back from the child. - Received events are handled by the parent update function,
pudate
which updates state to the new value.
Example 2 : Two Counters
How can we extend the pattern above to handle two counters? Let’s introduce a Counters component that will simply be instantiated by App.
Counters provides this View:
And uses this controller:
And that’s it:
- Nothing needs to happen to the Counter component - it is entirely reusable as is;
- We have a model with two parts
- we pass different updater functions to each Counter by creating closures (‘partially applying’) of
pupdate
.
Example 3 : Many Counters
OK, two was good but what about a flexible number? Again, we won’t need to touch Counter
, but we will need some additional tools for its parent, Counters
.
This time our model has become an array, and we need a method to add elements to the model. pupdate
does not need changing but note that it’s modelName
parameter now will be the index in the model Array. (We also bring in the NgFor
Directive, which we need in the rewritten View.)
Example 4: Adding an extra action
In example 3 we could only add a Counter
. What if we want to remove them too? This time we will need to change Counter
a little to add <button (click)="remove()">X</button>
to the View and implement the remove
method to emit a second type of Event up to the parent:
Counters
needs to provide for this extra event when it instantiates new counter
elements:
And premove
works equivalently to addCounter
to update the global state:
Using Redux & immutable for state management
So far, we have used mutable state, but it straightforward to add in calls to redux by the parent node of our Angular 2 app.As the focus here is on Angular I won’t present all of the Redux and immutable data handling code, but just show my Angular code’s redux calls. You can find the full code in the repo.
Conclusions
We have seen a succession of examples that added functionality while retaining a single source of state truth. Rendering was effectively ‘pure’ and actions were transmitted to the parent to maintain the state. The Angular loop is still quite different from Elm’s but we have achieved some of the core elements on the one-way data binding pattern.