Angular JS series – dirty checking – the digest cycle and $watch / $apply

Angular JS series – dirty checking – the digest cycle and $watch / $apply

Angular JS series – dirty checking – the digest cycle and $watch / $apply

In a previous post we had a look at AngularJS Two-Way Data Binding. To fully understand how the AngularJS data binding works, let’s take a peek on how AngularJS works underneath the hood. How does this magical data binding work?

To fully understand this principle it’s important to address some key principles as dirty checking – $digest loop – $watch and $apply method.

 

Dirty Checking

AngularJS compares a value with its previous value to determine if the value has changed. If the current value has been changed upon the previous value then a change event is fired. This is dirty checking in a nutshell. Dirty checking is the key behind the two way databinding principle of AngularJS.

AngularJS implements dirty checking for it’s two way data binding process on $scope variables. In AngularJS, a digest is the cycle that performs dirty checking. This is initiated via $digest(). If something happens outside of AngularJS, you need to let AngularJS know to execute a digest cycle and you do that via $apply which calls $digest.

As stated we have 2 contextual cases. Within the Angular context the $watch hook will automatically be attached. When you want to add a manual watcher (on a value from outside the Angular context), we’ll have to attach a watcher manually. See below.

 

What is a digest cycle ($digest)?

AngularJS defines the concept of digest cycle. This cycle can be considered as a loop, during which AngularJS checks if there are any changes to the watched variables on the the $scope and containing child $scopes.

Not everything on the $scope is being watched, luckily. If you would watch for changes to every object on the $scope, a digest loop would take ages to evaluate and performance issues would quickly arise. Only ‘watched’ variables are dirty-checked for changes. (see ‘register $scope variables to the digest cycle’)

/* trigger the digest cycle */
$scope.$digest();

 

Register $scope variables to the digest cycle

There are two ways of telling AngularJS to add a watcher on a $scope variable. When a variables is marked to be watched, a watcher is added to the digest cycle.

1. By using it in your view template through the curly brackets expression {{myVar}}.

2. By adding it manually through the $watch service (Controller level).

$scope.$watch('vm.name', function(current, original) {
/* check current with original for change – dirty checking */
});

 

$$phase flag indication digest cycle

$$phase is a flag set to indicate if AngularJS has an active $digest cycle running.
An error will occur if a $apply is invoked during an active $digest cycle.

Error : $apply already in progress

Note!
$$ means private context within AngularJS (core functions), so be aware that the interface could be unstable, best to prevent usage of such private objects / variables.

 

What is a $watch?

$watches can be used to watch any value, and trigger a function call when that value changes.

• A watcher can evaluate any value.
• A watcher’s handler can execute anything when aforementioned value has changed.
• All watchers are evaluated when $digest() is called.
• If the first argument of a $watch is a string, it is $eval’ed into a function prior to registration. It’s functionally equivalent to passing a function as the first argument, just with an extra step internally.

 

What is a $watchCollection?

$watch with only 2 parameters, is fast. However, Angular supports a 3rd parameter to this function, that can look like $watch(‘value’,function(){},true). The third parameter, tells Angular to perform deep checking, meaning to check every property of the object, which could be fairly expensive / overkill.

To address this performance issue, angular added $watchCollection(‘value’,function(){}). $watchCollection acts almost like $watch with a 3rd parameter, except it only checks the first layer of object’s properties, thus greatly improving the performance.

 

Consulting listed $scope watchers

The $$watchers variable on the $scope object holds all the active watchers that you have defined.
Console.log your $scope object to have a closer look at the watcher collection for that specific $scope.

 

Removing $scope watchers

Any manually added $watch can also be removed. $watch returns a deregistration function. Calling it directly will deregister the $watchers.

var listener = $scope.$watch("quartz", function () {});
listener(); // Will clear the watch

 

What is $apply?

This core method lets you to start the digestion cycle explicitly. That means that all watchers are checked; the entire application starts the $digest loop. Internally, after executing an optional function parameter, it calls $rootScope.$digest().

So, if you’re calling it by itself without passing an argument to it, you may as well just call $digest(). As a result of this, a digest cycle starts at the $rootScope, and subsequently visits all the child $scopes calling the watchers along the way.

 

$apply vs $digest

Simply put, $apply will start the $digest loop for the entire application. Starting from $rootScope up to all child $scope objects.

While $scope.$digest will start the digest cycle for the current scope and all the child $scopes. All the above scope objects nor $parentScope will not be checked nor affected.

 

When to call $apply manually?

If AngularJS usually wraps our code in $apply() and starts a $digest cycle, then when do you need to do call $apply() manually? Actually, AngularJS makes one thing pretty clear. It will account for only those model changes which are done inside AngularJS’ context (i.e. the code that changes models is wrapped inside $apply()). Angular’s built-in directives already do this so that any model changes you make are reflected in the view. However, if you change any model outside of the Angular context, then you need to inform Angular of the changes by calling $apply() manually. It’s like telling Angular that you are changing some models and it should fire the watchers so that your changes propagate properly.

 

How many time does the $digest cycle run?

When a $digest cycle runs, the watchers are executed to see if the scope models have changed. If they have, then the linked listener functions are invoked. But what if a listener function itself changed any scope model?

AngularJS accounts for that change as following; The $digest loop doesn’t run just once. At the end of the current loop, it starts all over again to check if any of the models have changed. This is done to account any model changes that might have been done by listener functions, in the previous loop. So, the $digest cycle keeps looping until there are no more model changes, or it hits the max loop count of 10. It’s always good to try to minimize model changes inside the listener functions. To prevent following extra cycle loops.

At a minimum, $digest will run twice, even if your listener functions don’t change any models. Even at the minimum loop it runs one extra time to make sure the models are stable and there are no further changes triggered.

 

$watch / $digest & $apply on native AngularJS elements

With above information about the digest cycle, as a summary, it’s good to know and realize how AngularJS implements these on its native elements.

Reference watches are set on
$scope.$watch
{{ }} (curly brackets) type bindings
• Most directives like : ng-show, ng-hide, …
• Scope variables scope: { bar: ‘=’}
• Filters {{value | myFilter}}
ng-repeat

Watchers (digest cycle) run on
• User action ng-click,… Most built in directives will call $scope.apply upon completion which triggers the digest cycle.
ng-change
ng-model
$http events (so all ajax calls)
$q promises resolved
$timeout
$interval
• Manual calls to $scope.apply and $scope.digest
• One-time bindings

 

$watch / $digest & $apply guidelines summarized

Having some basic background around the $watch / $digest and $apply principles, there are some practical guidelines.

$watch
• Use $watch in directives to update the DOM when a $scope value changes.
• Don’t use $watch in a controller. It’s completely unnecessary in almost every case. Use a method on the scope to update the value(s) the watch was changing instead.

$digest / $apply
Use $digest / $apply in directives to let Angular know you’ve made changes after an asynchronous call, such as a DOM event.
• Use $digest / $apply in services to let Angular know some asynchronous operation has returned, such as a WebSocket update,…
Don’t use $digest / $apply in a controller. This will make your code harder to test, and asynchronous operations outside of the Angular framework don’t belong in your controllers. They belong in services and directives.


About the Author

Daan is a Creative-Geek who loves learning and sharing new techniques! Follow him on Twitter to keep up to date with the Creative-Geeks blog and other subjects. Contact him on e-mail : info[at]creative-geeks.com.