Understanding Angular's $apply
and $digest
methods require a bit of background on how the browser renders the DOM. The browser has an event loop that will tell it how to display a page.
The browser's event loop initiates when any of the three events occur:
- User interaction, e.g. clicking a page
- A timer event, e.g. a timeout
- A network response
When an event happens, its callback will execute, and we enter the JavaScript context. During this step, the JavaScript code can modify the DOM structure.
Once the callback finishes executing, we leave the JavaScript context, and the browser re-renders the view based on the changes.
TL;DR
scope.$digest()
will fire off all watchers on the current scope and child scopesscope.$apply(fn)
will executefn
, and kick off a full$digest
loop
Angular's $digest loop
Angular's JavaScript context modifies the original event loop by providing its own event loop, called the $digest
loop, still within the browser's JavaScript context. This is how you benefit from data-binding, exception handling, etc from Angular.
The $digest
loop is made up of two smaller loops:
- one for processing the
evalAsync
queue - one for detecting changes in the
$watch
list
The $evalAsync
queue is a queue used to schedule work that happens before browser rendering. The $watch
list is a list of expressions that may have changed as a result of the work done during the $evalAsync
queue.
As Angular goes through the $watch
list, it uses each watcher function to decide whether the value has changed or not. This process is called dirty checking.
The loop will keep iterating until it does not have anything left in the $evalAsync
queue and no changes are detected in the $watch
list.
Once Angular reaches the last watcher, it's not quite done. It will run through the loop again to see if any values have been changed due to later evaluations. It will do this up to 10 times. If it finds that there have been no changes, the $digest
loop is complete. If it finds that values are still changing, and it has looped 10 times, Angular will throw an error.
$apply()
$apply()
is a method on scope
that takes a stimulusFn
, which is a function that you want to execute in an angular context. When you call scope.$apply(stimulusFn)
, you're telling Angular to kick off a $digest
loop.
The pseudocode for $apply()
is:
function $apply(expr) {
try {
return $eval(expr)
} catch (e) {
$exceptionHandler(e)
} finally {
$root.$digest()
}
}
If you're trying to run code outside of an Angular context that is updating something in an Angular context, you might want to use call $apply()
. For example, if you wanted to use setTimeout
, you would call $apply()
so that values are updated in the Angular context.
var callback = function () {
// update a value in Angular context
}
setTimeout(function () {
scope.apply(callback)
}, 500)
Generally, you do not need to call $apply()
manually. The example above could be achieve the same effect by calling Angular's $timeout
instead of setTimeout
.
$digest()
When you call $digest()
, you're telling Angular to run a $digest
on its current scope and all of its children. Other scopes are not affected. If you do not need to check every single watcher, $digest()
might increase your performance over $apply()
, which calls $digest()
from the $rootScope
.
One thing to note is that $digest()
does provide exception handling in the way $apply()
does.