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:

Be aware that since angular 1.0.0rc1 you need to specify ng-model-instant (docs-next.angularjs.org/api/…) to have your moder updated insantly. Otherwise it will be updated on blur event.
–
SotomajorMar 21 '12 at 15:30

5 Answers
5

AngularJS remembers the value and compares it to 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:

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 no proxies on old browsers. The bottom line is that dirty-checking allows you to do POJO, whereas KnockoutJS and Backbone.js force you to inherit from their classes, and access your data through accessors.

Change coalescence. Suppose you have an array of items. Say you want to add items into an array, as you are looping to add, each time you add you are firing events on change, which is rendering the UI. This is very bad for performance. What you want is to update the UI only once, at the end. The change events are too fine grained.

Change listeners fire immediately on a setter, which is a problem, since the change listener can further change data, which fires more change events. This is bad since on your stack you may have several change events happening at once. Suppose you have two arrays which need to be kept in sync for whatever reason. You can only add to one or the other, but each time you add you fire a change event, which now has an inconsistent view of the world. This is a very similar problem to thread locking, which JavaScript avoids since each callback executes exclusively and to completion. Change events break this since setters can have far reaching consequences which are not intended and non obvious, which creates the thread problem all over again. It turns out that what you want to do is to delay the listener execution, and guarantee, that only one listener runs at a time, hence any code is free to change data, and it knows that no other code runs while it is doing so.

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.

+1 this should be put on the angular docs!
–
sharp johnnyMar 27 '12 at 10:31

18

@Mark - yes, in KO you just add .extend({ throttle: 500 }) to wait 500 milliseconds after the last change event before acting on it.
–
Daniel EarwickerMar 13 '13 at 9:07

82

This entire answer is great other than "As long as they get 50 fps, any performance over that is a waste, since the human eye can not appreciate it, so you're better off drawing more stuff, than getting fps higher." That statement is completely incorrect depending on your application. The eye can definitely appreciate more than 50 fps, and as the various problems with VR show (read any of the latest from John Carmack or Michael Abrash, especially the latter's GDC 2013 VR talk), 50 fps is actually way too slow. Other than that, your answer is great. I just don't want misinformation spreading.
–
Nate BundyApr 25 '13 at 15:35

7

@DavidRivers us is µs just like in utorrent 1µs = 0.000001s
–
ThorgeirMay 14 '13 at 15:08

39

Just wondering, if your app is like Twitter or a comment thread/forum, and you implement infinite scrolling based on Angular, you could run into the "2000 pieces of info" "limit". A single comment could easily have several variables for the author's name, profile img, content, datetime, and etc. Also, say we have a giant array for storing all the comments/posts, every dirty checking would require scanning of this array, am I right? This would make the browser a bit laggy at times which is a bad user experience. What do you suggest we do in this case to ensure reasonable performance?
–
LucasJul 4 '13 at 9:36

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 is 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 combobox 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 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 using ngOnce, deregistering watchers and similar tricks, or construct directives which builds 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...

Yeah I second this. Our app's primary responsibility is to display connections between different entities. A given page might have 10 sections. Each section has a table. Each table has 2-5 typeahead filters. Each table has 2-5 columns, each with 10 rows. Very quickly we run into perf issues, and going with the "similar tricks" options.
–
Scott SilviSep 1 '13 at 15:04

5

Is it fair to say that Angular is not only about data binding and some apps may not want to use this feature for exactly the reasons others have cited? I think the approach of DI and modularity is itself worth a lot; having magic auto-binding is nice but in every existing implementation has trade-offs of performance. Angular's way is arguably superior for the majority of CRUD web apps, and people are just hitting a wall by trying to take it to extremes. It would be nice to have an alternate method of event listening supported, but maybe that's fundamentally too complex for a single framework?
–
Jason BoydFeb 25 '14 at 21:52

3

Angular now has one way and bind-once databinding to help with this problem. Furthermore it now has indexes for your repeater source, which lets you modify the list without rebuilding the dom for the entire content.
–
MithonMar 3 '14 at 11:34

3

@MW. Honestly I thought bind-once was in the core. But it appears it's not. It's just something you can do when writing your own directives, basically linking stuff without watching them. However there is a ux mod for it: github.com/pasvaz/bindonce
–
MithonMar 3 '14 at 16:26

Items are watched by passing a function (returning the thing to be
watched) to the $watch method.

Changes to watched items must be made within a block of code
wrapped by the $apply method.

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 angular compiler to create the watches for you and controller methods are run inside $apply already. So to the app developer it is all transparent.

@remi, I am not concerned about the last version of AngularJS. Are they already using proxies or Object.observe? If not, they are still in the dirty checking era, which builds a timed loop to see if model attributes have changed.
–
Eliseu MonarMay 4 '14 at 1:53

I wondered this myself for awhile. Without setters how does Angular notice changes to the $scope object? Does it poll them?

What it actually does is this: Any "normal" place you modify the model was already called from the guts of Angular, so it automatically calls $apply for you after your code runs. Say your controller has a method that's hooked up to ng-click on some element. Because Angular 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 Angular so it does the $apply.

When the docs talk about having to call $apply manually for code outside of angular, it's talking about code which, when run, doesn't stem from Angular itself in the call stack.

It happened that I needed to link a data model of a person with a form, what I did was a direct mapping of the data with the form.

For example if the model had something like:

$scope.model.people.name

The control input of the form:

<input type="text" name="namePeople" model="model.people.name">

That way if you modify the value of the object controller, this will be reflected automatically in the view.

An example where I passed the model is updated from server data is when you 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 I worked very well, what does happen, is that angularJS sometimes takes a few seconds to refresh the model, to do this you can put a spinner while displaying the data.