How does data binding work in the AngularJS framework?
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 we call when we 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 (We 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:
- The syntax is atrocious, since browsers do not support it natively. Yes, there are proxies, but they are not semantically correct in all cases, and of course there are no proxies on old browsers. The bottom line is that dirty-checking allows we to do POJO, whereas KnockoutJS and Backbone.js force we to inherit from their classes, and access our data through accessors.
- Change coalescence. Suppose we have an array of items. Say we want to add items into an array, as we are looping to add, each time we add we are firing events on change, which is rendering the UI. This is very bad for performance. What we want is to update the UWE only once, at the end. The change events are too fine-grained.
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 - We 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 we 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: 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 we 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 we don't know what we are doing. But we hope to have an answer by providing an instrumentation module, which would show we 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.
Misko already gave an excellent description of how the data bindings work, but We would like to add my view on the performance issue with the data binding.
As Misko stated, around 2000 bindings is where we start to see problems, but we 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 we start building any sort of widget or data grid with two-way binding we can easily hit 2000 bindings, without having a bad ux. Consider, for example, a combobox where we 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) we 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 said country, and the third to select a hotel) and we 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 we build this with ng-repeats, and/or have information in some cells which uses some bindings, we could be approaching 2000 bindings with this grid alone.
We 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 using ngOnce, deregistering watchers and similar tricks, or construct directives which builds the DOM with jQuery and DOM manipulation. WE feel this defeats the purpose of using Angular in the first place. WE would love to hear suggestions on other ways to handle this, but then maybe WE should write my own question. WE 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.
This is my basic understanding. It may well be wrong!
- Items are watched by passing a function (returning the thing to be watched) to the $watchmethod.
- Changes to watched items must be made within a block of code wrapped by the $applymethod.
- At the end of the $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.
- If any changes are found then the digest is invoked again until all changes stabilize.
In normal development, data-binding syntax in the HTML tells the AngularJS compiler to create the watches for we and controller methods are run inside $apply already. So to the application developer it is all transparent.
WE wondered this myself for a while. Without setters how does AngularJS notice changes to the $scope object? Does it poll them? What it actually does is this: Any "normal" place we modify the model was already called from the guts of AngularJS, so it automatically calls $apply for we after our code runs. Say our controller has a method that's hooked up to ng-click on some element. Because AngularJS wires the calling of that method together for you, it has a chance to do an $apply in the appropriate place. Likewise for expressions that appear right in the views, those are executed by AngularJS so it does the $apply. When the documentation talk about having to call $apply manually for code outside of AngularJS, it's talking about code which, when run, doesn't stem from AngularJS itself in the call stack.
It happened that WE needed to link a data model of a person with a form, what WE did was a direct mapping of the data with the form. For example if the model had something like:
The control input of the form:
That way if we modify the value of the object controller, this will be reflected automatically in the view. An example where WE passed the model is updated from server data is when we ask for a zip code and zip code based on written loads a list of colonies and cities associated with that view, and by default set the first value with the user. And this WE worked very well, what does happen, is that angularJS sometimes takes a few seconds to refresh the model, to do this we can put a spinner while displaying the data.