Best practice to include code within the cycle digest in AngularJs

Table of Content

I was converting a DurandalJs (using BreezeJs for the async calls) application to AngularJs, the team decided to keep using BreeseJs with AngularJs, so I started notice that when I update a $scope variable (to update the view) the change wasn’t reflecting in the view.
I realized that the problem was that when you try to update an $scope variable when a promise come back, this operation is done outside the angular digest cycle, and that’s why it was not updating the view.
This only happens when you deffer a call outside Angular (you can prevent this using the $http and $q services).

The first solution I came across, was to call $scope.$apply(). But I start receiving this error:
Error: $digest already in progress

It means your $scope.$apply() isn't high enough in the call stack (digest cycle).

I kept looking and I found this code.

if(!$scope.$$phase) {
   //$digest or $apply

It looks a little hacky, well, it is!
It is considered an Anti-Pattern (2- Don't do if (!$scope.$$phase) $scope.$apply(), it means your $scope.$apply() isn't high enough in the call stack.):

I thought to myself, there should be a way to do this, and I found this, this is the solution:

    //this code is included in the digest cycle

Also there is another safe option if you have underscore (this is not the best practice either):


Things you should know:

  • $$phase is indeed private to the framework and there are good reasons for that
  • $timeout(callback) will wait until the current digest cycle (if any) is done, then execute your code, then run at the end a full $apply
  • $timeout(callback, delay, false)will do the same (with an optional delay before executing your code), but won't fire an $apply which saves performances if you didn't modify your model
  • $apply invokes, among other things, $root.$digest, which means it will redigest the root scope and all of its children, even if you're within an isolated scope.
  • $digest will simply sync its scope model to view, but won't tell its parents scope, which can save a lot of performances when working on an isolated part of your HTML with an isolated scope (from a directive)
  • $evalAsync has been introduced with angularjs 1.2, that will probably solve most of your troubles, please refer to the last paragraph to learn more about it
  • if you get the "$digest already in progress" error, then your architecture is wrong: either you don't need to re-digest your scope, or you should not be in charge of that.

Here is a jsFiddle working example.

Please feel free to leave a comment with questions. 😉