Before I dive into the details of the Aurelia data-binding system, I thought some context might be useful. I’ve used various data-binding systems over the last few years, and each has its own tradeoffs. These tradeoffs are useful in highlighting what makes Aurelia’s adaptive data-binding method different/awesome.
The first front end framework that I used for an application of significant size was BackboneJS. BackboneJS is event driven, meaning that when a change occurs on a model or collection, an event is fired. Subscribers can then listen for this event and take the necessary action, such as updating the UI or pushing changes to the back end. The advantage of this method is speed, since objects are responsible for emitting an event as they are changed there is no overhead of an orchestration process checking the entire object graph for changes. The downside of the BackboneJS binding system there is no two way data-binding built in, meaning that simple tasks such as taking updated data from input elements on a form needs to be handled manually. This is not a show stopper as there are a few good data-binding frameworks such as exproxy which will do this for you.
KnockoutJS is a more lightweight framework than BackboneJS, and is focused on being really good at data-binding.
With Knockout we can set up a simple text binding like this:
Knockout allows you to data-bind in just about any way you can imagine. This is the list from the Knockout documentation:
- visible binding
- text binding
- html binding
- css binding
- style binding
- attr binding
It supports down to IE6 which is a definite advantage, but there are two drawbacks:
- You run a method on the property in the view-model you wish to bind to to set up data-binding. Properties on a view-model are not automatically bindable
- The syntax for modifying a ‘property’ on a knockout view-model is more clunky (you need to call the ‘property’ like a method so that the framework knows what has changed).
Knockout has been around since 2010 and works with IE6. This explains the above drawbacks that I’ve mentioned. If it were re-written today with the same browser support matrix as Aurelia the binding syntax may have looked quite similar.
AngularJS by contrast uses the concept of dirty checking to implement two way data-binding. Each time that a variable is created on the $scope it is registered to be watched as a part of the AngularJS digest loop. You can then specify that a variable should be watched by binding to it in the UI or manually creating a watch.
The benefit of this approach is simplicity. The process required to implement binding on a particular property is straightforward. All you do is declare the value on the $scope object, bind to it in the view and everything is magically wired up for you. The downside is the performance cost of dirty checking. Typically if an application is structured correctly this is not a problem in modern browsers, but it is easy to shoot yourself in the foot as a novice user.
The binding above from KnockoutJS would look something like this in AngularJS:
And we finally come to Aurelia. Aurelia uses a combination of strategies to implement the most efficient data-binding technique based on the environment and implementation details.
The priority order is as follows:
- If the object being observed is a DOM element then one of the DOM element observe strategies will be used such as the SelectValueObserver or the ValueAttributeObserver . In this case Aurelia selects the appropriate binding based on the elements type name and the name of the attribute we are binding.
- Aurelia then checks if a property has been defined using the Object.DefineProperty syntax with a getter. In this case Aurelia will check whether there are any dependencies on the property. Aurelia is pluggable, so it also checks at this point whether a property observation adapter is available to complete the binding. Here we could plug in something like KnockoutJS or Breeze. If none are defined then Aurelia falls back to dirty checking. They may be able to switch the implementation out for Object.getNotifier once browser support improves.
- In the final case Aurelia will treat this as a standard property. Ideally it uses Object.observe where the browser supports it. If this is not supported then Aurelia uses the Object.defineProperty method to re-write the property on the object so that it can observe any property changes.
The Aurelia version of our status binding expression might look like this:
There are two major benefits of the Aurelia system. Firstly, the system is pluggable. This gives us the option of switching in an alternative binding method if desired. The second and most important point is that it gives us the ease of use that we saw with the AngularJS data-binding system, combined with the speed that we see with KnockoutJS. In other words we can have our cake and eat it to.