How does data binding work in the AngularJS
framework?
I haven't found technical details on their site. It's more or less clear how it works when data is propagated from view to model. But how does AngularJS track changes of model properties without setters and getters?
I found that there are JavaScript watchers that may do this work. But they are not supported in Internet Explorer 6 and Internet Explorer 7. So how does AngularJS know that I changed for example the following and reflected this change on a view?
myobject.myproperty="new value";
Misko already gave an excellent description of how the data bindings work, but I would like to add my view on the performance issue with the data binding.
As Misko stated, around 2000 bindings are where you start to see problems, but you shouldn't have more than 2000 pieces of information on a page anyway. This may be true, but not every data-binding is visible to the user. Once you start building any sort of widget or data grid with two-way binding you can easily hit 2000 bindings, without having a bad UX.
Consider, for example, a combo box where you can type text to filter the available options. This sort of control could have ~150 items and still be highly usable. If it has some extra feature (for example a specific class on the currently selected option) you start to get 3-5 bindings per option. Put three of these widgets on a page (e.g. one to select a country, the other to select a city in the said country, and the third to select a hotel) and you are somewhere between 1000 and 2000 bindings already.
Or consider a data-grid in a corporate web application. 50 rows per page is not unreasonable, each of which could have 10-20 columns. If you build this with ng-repeats, and/or have information in some cells which uses some bindings, you could be approaching 2000 bindings with this grid alone.
I find this to be a huge problem when working with AngularJS, and the only solution I've been able to find so far is to construct widgets without using two-way binding, instead of using ngOnce, deregistering watchers and similar tricks, or construct directives which build the DOM with jQuery and DOM manipulation. I feel this defeats the purpose of using Angular in the first place.
I would love to hear suggestions on other ways to handle this, but then maybe I should write my own question. I wanted to put this in a comment, but it turned out to be way too long for that...
TL;DR
The data binding can cause performance issues on complex pages.
Obviously there is no periodic checking of
Scope
whether there is any change in the Objects attached to it. Not all the objects attached to scope are watched . Scope prototypically maintains a $$watchers .Scope
only iterates through this$$watchers
when$digest
is called .Angular adds a watcher to the $$watchers for each of these
$watch function takes in three parameters:
There is an interesting thing in Angular called Digest Cycle. The $digest cycle starts as a result of a call to $scope.$digest(). Assume that you change a $scope model in a handler function through the ng-click directive. In that case AngularJS automatically triggers a $digest cycle by calling $digest().In addition to ng-click, there are several other built-in directives/services that let you change models (e.g. ng-model, $timeout, etc) and automatically trigger a $digest cycle. The rough implementation of $digest looks like this.
If we use JavaScript’s setTimeout() function to update a scope model, Angular has no way of knowing what you might change. In this case it’s our responsibility to call $apply() manually, which triggers a $digest cycle. Similarly, if you have a directive that sets up a DOM event listener and changes some models inside the handler function, you need to call $apply() to ensure the changes take effect. The big idea of $apply is that we can execute some code that isn't aware of Angular, that code may still change things on the scope. If we wrap that code in $apply , it will take care of calling $digest(). Rough implementation of $apply().
This is my basic understanding. It may well be wrong!
$watch
method.$apply
method.$apply
the$digest
method is invoked which goes through each of the watches and checks to see if they changed since last time the$digest
ran.In normal development, data-binding syntax in the HTML tells the AngularJS compiler to create the watches for you and controller methods are run inside
$apply
already. So to the application developer it is all transparent.AngularJS handle data-binding mechanism with the help of three powerful functions : $watch(),$digest()and $apply(). Most of the time AngularJS will call the $scope.$watch() and $scope.$digest(), but in some cases you may have to call these functions manually to update with new values.
$watch() :-
$digest() -
$apply() -
Here is an example of data binding with AngularJS, using an input field. I will explain later
HTML Code
AngularJS Code
As you can see in the example above, AngularJS uses
ng-model
to listen and watch what happens on HTML elements, especially oninput
fields. When something happens, do something. In our case,ng-model
is bind to our view, using the mustache notation{{}}
. Whatever is typed inside the input field is displayed on the screen instantly. And that's the beauty of data binding, using AngularJS in its simplest form.Hope this helps.
See a working example here on Codepen
AngularJS remembers the value and compares it to a previous value. This is basic dirty-checking. If there is a change in value, then it fires the change event.
The
$apply()
method, which is what you call when you are transitioning from a non-AngularJS world into an AngularJS world, calls$digest()
. A digest is just plain old dirty-checking. It works on all browsers and is totally predictable.To contrast dirty-checking (AngularJS) vs change listeners (KnockoutJS and Backbone.js): While dirty-checking may seem simple, and even inefficient (I will address that later), it turns out that it is semantically correct all the time, while change listeners have lots of weird corner cases and need things like dependency tracking to make it more semantically correct. KnockoutJS dependency tracking is a clever feature for a problem which AngularJS does not have.
Issues with change listeners:
What about performance?
So it may seem that we are slow, since dirty-checking is inefficient. This is where we need to look at real numbers rather than just have theoretical arguments, but first let's define some constraints.
Humans are:
Slow — Anything faster than 50 ms is imperceptible to humans and thus can be considered as "instant".
Limited — You can't really show more than about 2000 pieces of information to a human on a single page. Anything more than that is really bad UI, and humans can't process this anyway.
So the real question is this: How many comparisons can you do on a browser in 50 ms? This is a hard question to answer as many factors come into play, but here is a test case: http://jsperf.com/angularjs-digest/6 which creates 10,000 watchers. On a modern browser this takes just under 6 ms. On Internet Explorer 8 it takes about 40 ms. As you can see, this is not an issue even on slow browsers these days. There is a caveat: The comparisons need to be simple to fit into the time limit... Unfortunately it is way too easy to add a slow comparison into AngularJS, so it is easy to build slow applications when you don't know what you are doing. But we hope to have an answer by providing an instrumentation module, which would show you which are the slow comparisons.
It turns out that video games and GPUs use the dirty-checking approach, specifically because it is consistent. As long as they get over the monitor refresh rate (typically 50-60 Hz, or every 16.6-20 ms), any performance over that is a waste, so you're better off drawing more stuff, than getting FPS higher.