THE EVOLUTION OF WEB DATA BINDING

Have you ever talked passionately about data binding concepts with a backend engineer? I have, and most of the time they look at me as if I were a caveman who doesn’t speak their language… so for all you cavemen (and backend engineers) out there… Let’s get some things clear so that next time your frontend engineer friend starts talking, you’ll be able to join in.

Data binding is the process that keeps the UI synced with the data model. If the model was changed, then the UI should reflect the new state of the model. Unfortunately it is not supported by browsers out of the box (since HTML is a static language) and so many libraries and frameworks have been developed to fill this gap.

Let’s review the evolution of the main approaches and frameworks over the past few years and see where we are heading in the future..

(I will use a simple and naive application that randomizes a name for the user to demonstrate some of the concepts).

 

 

big bang

At the Beginning Of Time

As data binding is not supported by browsers it is the developer’s responsibility to do all the work for keeping the UI synced.

Before any library became widely used for this task, we used the browser’s native API to access the DOM element and refreshed it with the latest model value at the right time. Sounds easy? Not at all… Because the more features you have, the more models you need, and the more complex it is.

 

<button id="btn">Select Random Name</button>
<div>Your name is: <span id="name"></span></div>
<script>
var names = ['Jacob', 'William', 'Michael', 'James', 'Smith', 'Miller'];
var setName = function(name) {
var nameElemnt = document.getElementById("name");
nameElemnt.innerText = name;
}
var selectRandomName = function() {
var randomName = names[Math.floor(Math.random()*names.length)];
setName(randomName);
}
// Initial name and button event
var init = function(){
setName(names[0]);
var btn = document.getElementById("btn");
btn.onclick = selectRandomName;
}
init();
</script>

code in jsfiddle

 

 

dino

When Dinosaurs (jQuery) Ruled The Earth

jQuery is a library originally released in 2006 to make life much easier. It wrapped the browser’s API allowing better DOM element selection capabilities, event registering, AJAX calls and more. In terms of keeping the DOM synced it was just a small push as still the developer had to do it by himself, but it still helped since the work was done with better and easier API.

Although jQuery is ~9 years old (a dinosaur in terms of web libs) it is still one of the most popular web libraries today.

 

<button id="btn">Select Random Name</button>
<div>Your name is: <span id="name"></span></div>
<script>
var names = ['Jacob', 'William', 'Michael', 'James', 'Smith', 'Miller'];
var setName = function(name) {
var nameElemnt = $("#name");
nameElemnt.text(name);
}
var selectRandomName = function() {
var randomName = names[Math.floor(Math.random()*names.length)];
setName(randomName);
}
// Initial name and button event
var init = function(){
setName(names[0]);
var btn = $("#btn");
btn.click(selectRandomName);
}
init();
</script>

code in jsfiddle

 

 

Stanley Kubrick’s, Arthur C. Clarke’s, and Geoffrey Unsworth’s conjectural envisioning of the moment Australopithecus discovered tools and thus crossed a milestone today considered integral to intelligence. It is noteworthy that the group of early humans depicted, enlightened through extraterrestrial intervention, immediately co-opt their bestowed knowledge. Rather than directing their fresh understanding toward the more efficient collection of fruits, berries, and herbs, they focus on killing and suppressing both less advanced species as well as less knowledgeable fellow hominids.

(the dawn of man – Space odyssey)

 

JS Templates and One Time Binding

The JavaScript templates engine allows the developer to add JS functions and variables into the HTML tags which eventually will be evaluated and rendered into the DOM.

Each JS template engine has its own syntax and markups, but all share the same purpose –  to merge the model and the UI.

Problem is – while this merge was useful, it occurred only once, on initial rendering. After that, it was the developer’s responsibility to keep the template synced with the model. That’s why it was named ‘One Time Binding’ as the model is bound to the UI only once.

Backbone.js offered such templating capability using underscore.js, and is still widely used.

 

<button id="btn">Select Random Name</button>
<script type="text/html" id='container'>
<div>Your name is: <span id="name"><%= name %></span></div>
</script>
<div id="target"></div>
<script>
var names = ['Jacob', 'William', 'Michael', 'James', 'Smith', 'Miller'];
// Render the template with the data
var render = function(user){
var template = container.innerHTML;
target.innerHTML = _.template(template, user);
}
var selectRandomName = function() {
var randomName = names[Math.floor(Math.random()*names.length)];
render({name: randomName});
}
// Initial name and button event
var init = function(){
var btn = document.getElementById("btn");
btn.onclick = selectRandomName;
var user = {name: names[0]}
render(user);
}
init();
</script>

code in jsfiddle

 

 

one small step for man

Full Data Binding

Angular 1.x (also ember and knockout.js) provided a full data binding. Each variable (or function) can be fully bound to its UI component using simple syntax without the need to maintain the sync by code. You just set a variable on the scope and the magic starts – the framework will always keep the DOM and the js model synced. With Angular 1.x a full data binding was finally born into the world.

Such a powerful capability allows the developer to focus on the application and its logic without having to keep the DOM updated, and also reduces dramatically the number of lines of code and bugs, which became the responsibility of the framework.

Angular didn’t stop there. They also provided a two-way data binding allowing the UI component to update the model and vice versa.

 

<div ng-app>
<div ng-controller="MyCtrl">
<button ng-click="randomName()">Select Random Name</button>
<div>Your name is: {{name}}</div>
</div>
</div>
<script>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
var names = ['Jacob', 'William', 'Michael', 'James', 'Smith', 'Miller'];
var initCtrl = function(){
$scope.name = names[0];
}
$scope.randomName = function() {
$scope.name = names[Math.floor(Math.random()*names.length)];
}
initCtrl();
}
</script>

code in jsfiddle

However, those magical touches come with a high price:

  1. Poor performance – Mostly due to change detection process which runs in the background and compares the view value with the latest model value (digest loop in Angular 1.x).
  2. Scope soup – Adding more features implies adding more models and functions to the scope, which grows significantly. This anti-pattern leads to huge controllers which at some point can’t be maintained any more (scope soup example from the random name app).
  3. Infinite and long digest loops – as change event flows in 2 directions, from Model to UI and vice versa, you could easily end up with many digest loops or even with an infinite digest loop which causes the application to collapse.

 

 

reactjs

Think Reactive

In 2013 Facebook launched React.js, a library which is used for view rendering (some say it’s the V of the MVC).

React’s approach to data binding is very different to Angular’s, since the data can flow in one direction only – from parent component to its child, where each component has a simple input API for data. This way change detection is much easier and faster as many components’ subtrees can be skipped in this process if the parent state did not change.

However, in order to support such architecture the app has to be designed from the ground up as a hierarchical tree of smaller components.

React doesn’t have a full data binding like Angular 1.x. You still have to signal the framework on a change using setState() API, where in Angular 1.x you just change the model’s value in the scope. In addition React doesn’t support two-way data binding – a child component can’t change parent’s model, it can only passes events to the parent.

At this point, it seems like evolution has gone backwards, since we’re talking about reducing binding capabilities. However React has good reasons for doing it this way. They want to enforce a one directional data flow in the application, a flow which has a clear starting and ending points. That way, there are no infinite or long digest loops, nor big controllers.

Closing the data flow loop’ explicitly leads to more understandable and easier-to-maintain programs.

(from React two-way data binding helper page)

 

<div id="app">
</div>
<script>
// --- JSX format ---
// Name component
var Name = React.createClass({
render: function() {
return <div>{this.props.name}</div>
}
});
// App component
var App = React.createClass({
names: ['Jacob', 'William', 'Michael', 'James', 'Smith', 'Miller'],
handleClick: function() {
var randomName = this.names[Math.floor(Math.random()*this.names.length)];
this.setState({name: randomName});
},
getInitialState: function() {
return {name: this.names[0] }
},
render: function() {
return <div>
<button onClick={this.handleClick}>Select Random Name</button>
<Name name={this.state.name} />
</div>;
}
});
// Init the react root element
ReactDOM.render(
<App />,
document.getElementById('app')
);
</script>

code in jsfiddle

 

 

t2

The Future

Currently Angular 2 is in beta release but it seems like they have made a major change in the data binding approach, adopting many concepts from React (to avoid the performance issue and the anti-pattern discussed above).

Angular 2 has almost full data binding within the component itself. You set a variable on the component constructor and it will be fully synced to the component’s UI – you don’t need to signal the framework on a change (as in React setState() API). There will also be limited support for two-way data binding using the ngModel directive.

Angular 2 also requires small components tree architecture instead of large controllers to support the Reactive paradigm.This is a dramatic change from Angular 1.x. As a result the level of data binding is reduced, but the application will gain performance and scalability.

 

 

Conclusion

Few years back, at the dawn of frontend frameworks, full data binding seemed to be an ideal feature all frameworks must have.Later on we discovered the high price of this ideal, which made the major frameworks reduce the level of data binding they provide their users.

It looks like there is a trade off here: In order to gain performance and scalability the framework must enforce some rules and design on the application architecture.
In my opinion, the framework that will provide the best mix of the two sides of the equation will win… And hopefully some day it will be supported by the browser.