Writing Robust AngularJS Web Applications
To understand how AngularJS achieves its goals we need to remember that web
browsers have a single UI thread. There are other threads running in a browser (for
example, ones responsible for network-related operations) but only one thread is
available to render DOM elements, listen to DOM events, and execute JavaScript
code. Browsers constantly switch between the JavaScript execution context and the
DOM rendering context.
AngularJS makes sure that all the model values are calculated and "stable" before
giving control back to the DOM rendering context. This way UI is repainted in one
single batch, instead of being constantly redrawn in response to individual model
value changes. This results in faster execution (as there is less context switching) and
better visual effect (as all the repaints are done once only). Repainting UI after each
and every single model property change would give us slow and flickering UI.
Anatomy of a $watch
AngularJS uses dirty checking mechanism to determine if a given model value has
changed. It works it out by comparing previously saved model values with the ones
computed after one of the events that can result in model mutation occurs (DOM
events, XHR events, and so on).
As a reminder, here is the general syntax to register a new watch:
$scope.$watch(watchExpression, modelChangeCallback)
When a new $watch is added on a scope AngularJS evaluates the watchExpression
and internally saves the result of this evaluation. Upon entering the $digest loop the
watchExpression will be executed once again, and a new value will be compared
to the saved one. The modelChangeCallback will be executed only if the new value
differs from the previous one. The new calculated value is also saved for further
comparisons and the whole process can be repeated.
As developers we will be well aware of watches that are registered manually
(in application controllers or custom directives). But we need to remember that
any directive (both from the AngularJS core set of directives as well as from
third party collections) can set up its own watches. Any interpolation expression
({{expression}}) will register a new watch on a scope as well.
Model stability
AngularJS considers that a model becomes stable (and we can move to UI rendering)
if none of the watches detects any further changes. It is enough that one watch sees a
change to mark the whole $digest loop as "dirty", and force AngularJS into another
turn of the loop. It is the "one bad apple spoils the whole bunch" principle, and there
are good reasons for it.