Angular is known for its powerful two-way data-binding, but with the new release of AngularJS 1.5, we’ve got one-way data binding (one-directional) binding capabilities inside our Components and Directives. Woohoo! Let’s explore to see what it does, and doesn’t, give us to develop with.
Table of contents
First impressions
When I setup an example jsFiddle to test this new feature out, I used an Object with a name
property, then manipulated it through isolate bindings, only to find that Angular updated the Object and it was in fact still two-way data-binding (well, somewhat). Objects in JavaScript are bound by reference, and Angular doesn’t make a copy of the Object when passing into one-way bindings, it actually sets the same value, which means Objects are somewhat two-way bound still.
Directive/Component API
For this article, I’ll be using the new Component method. The syntax is the same for both Directives and Components, so you’ll pick it up easy.
Two-way binding
Before we even look at what one-way binding does, it makes sense to provide a two-way binding example for us to compare against.
Let’s create a component definition with respective parent
Controller and some bindings and functions to let us manipulate some data:
var example = {
bindings: {
obj: '=',
prim: '='
},
template: `
<div class="section">
<h4>
Isolate Component
</h4>
<p>Object: {{ $ctrl.obj }}</p>
<p>Primitive: {{ $ctrl.prim }}</p>
<a href="" ng-click="$ctrl.updateValues();">
Change Isolate Values
</a>
</div>
`,
controller: function () {
this.updateValues = function () {
this.prim = 10;
this.obj = {
john: {
age: 35,
location: 'Unknown'
}
};
};
}
};
function ParentController() {
this.somePrimitive = 99;
this.someObject = {
todd: {
age: 25,
location: 'England, UK'
}
};
this.updateValues = function () {
this.somePrimitive = 33;
this.someObject = {
jilles: {
age: 20,
location: 'Netherlands'
}
};
};
}
angular
.module('app', [])
.component('example', example)
.controller('ParentController', ParentController);
Based on my initial impressions, it makes sense to learn about the new API using Objects and Primitive values to see how things differ, so we’ll be doing exactly that.
Free eBook
Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- Setters and Getters for Styles and Class Bindings
Let’s bootstrap the app and get it running with some HTML, passing in attributes for someObject
and somePrimitive
for the isolate two-way bound Directive, but also let’s keep a reference to the parent values so we can see what changes:
<div ng-app="app">
<div ng-controller="ParentController as parent">
<h3>
Two way data-binding
</h3>
<div class="section">
<h4>
Parent
</h4>
<p>
Object: {{ parent.someObject }}
</p>
<p>
Primitive: {{ parent.somePrimitive }}
</p>
<a href="" ng-click="parent.updateValues();">
Change Parent Values
</a>
</div>
<example obj="parent.someObject" prim="parent.somePrimitive"></example>
</div>
</div>
Live embed:
With two-way binding, if I change the reference Object and Primitive in the parent and the isolate Component, you’ll see both values continue to update. Now let’s look at one-way flow.
One-way bindings
First, the syntax. Instead of bindings: { obj: '=' }
, we’re going to need the new <
notation. Let’s first replace our Component definition Object with that:
var example = {
bindings: {
obj: '<',
prim: '<'
},
template: `
<div class="section">
<h4>
Isolate Component
</h4>
<p>Object: {{ $ctrl.obj }}</p>
<p>Primitive: {{ $ctrl.prim }}</p>
<a href="" ng-click="$ctrl.updateValues();">
Change Isolate Values
</a>
</div>
`,
controller: function () {
this.updateValues = function () {
this.prim = 10;
this.obj = {
john: {
age: 35,
location: 'Unknown'
}
};
};
}
};
function ParentController() {
this.somePrimitive = 99;
this.someObject = {
todd: {
age: 25,
location: 'England, UK'
}
};
this.updateValues = function () {
this.somePrimitive = 33;
this.someObject = {
jilles: {
age: 20,
location: 'Netherlands'
}
};
};
}
angular
.module('app', [])
.component('example', example)
.controller('ParentController', ParentController);
Go ahead and touch the live demo. You’ll be able to change the isolate bindings without affecting the parent
scope. However, the $watch
is setup on the parent
data source, so when changes occur, it’ll propagate down and flow into the Component to update it with new data. One-way binding: it’s as easy as that.
… Or is it?
There’s a small few things to keep in mind when using one-way bindings (as mentioned in the “First impressions” paragraph).
Caveats
In one-way binding, Object values are set to the isolate Component using the same value, the parent Object’s identity. When we break this identity (like I did above in the examples), we’re technically “breaking” that identity binding and the parent’s $watch
will not fire. Objects are bound by reference, which means that if we change a property, it’ll still kind-of two-way bind:
Our isolate property parent.someObject
is isolated as obj
, which means we can access this.obj.todd.age
to get my age. We can also mutate this property by setting a new value. This will force the parent
to also update as Objects are bound by reference and not copied by Angular.
I’ve omitted the Primitive
properties for this as it’s not what we’re interested in:
Notice how age: 25
becomes age: 26
in both and back again when you change the parent value.