Angular 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 non-angular world into angular world, calls $digest()
. A digest is just plain old dirty-checking. It works on all browsers and is totally predictable.
To contrast dirty-checking (angular) vs change listeners (KO, backbone): 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. KO dependency tracking is a clever feature for a problem which angular 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. Bottom line is that dirty-checking allows you to do POJO, whereas KO and backbone 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 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 JS 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 then just have theoretical arguments, but first lets define some constraints.
Humans are:
slow — Anything faster than 50ms 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: can you do 2000 comparisons in 50 ms even on slow browsers? That means that you have 25µs per comparison. I believe this is not an issue even on slow browsers these days. There is a caveat: the comparisons need to be simple to fit into 25µs. Unfortunately it is way too easy to add slow comparison into angular, so it is easy to build slow apps 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.
Turns out that video games and GPUs use the dirty-checking approach, specifically because it is consistent. 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.