When is angularjs `$digest` triggered?

1.1k views Asked by At

I am trying to implement a function to check for user inactivity. My online search has suggested that I can use the $rootScope.$watch to monitor activity. However, I am a bit concerned about the $digest trigger... as I can't seem to be able to find exactly when or which events trigger this. Any pointers will be appreciated.

3

There are 3 answers

8
willoller On

Angular triggers $digest (generally) all by itself. Your $watch should "just work".

Oh - if you really need to initiate the $digest loop yourself, you should use $scope.apply() instead of calling digest directly.


What really happens behind the scenes though is all kinds of things actually trigger a $digest, including ng-click events, timeouts, watchers, long-polling, websockets, http calls, promises - anything that changes the internal application state that angular manages (via "dirty checking"). For more info, see here: https://www.sitepoint.com/understanding-angulars-apply-digest/ and https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$digest

1
Jigar7521 On

To understand the Angular context, we need to look at exactly what goes on inside of it. There are two major components of the $digest loop:

When Angular thinks that the value could change, it will call $digest() on the value to check whether the value is “dirty.” Hence, when the Angular runtime is running, it will look for potential changes on the value.


$watch lists are resolved in the $digest loop through a process called dirty checking, so after the scope expression is evaluated and the $digest loop runs, the $scope’s watch expressions will run dirty checking.

During the $digest cycle, which executes on the $rootScope, all of the children scopes will perform dirty digest checking. All of the watching expressions are checked for any changes, and the scope calls the listener callback when they are changed.

For example

When we call the $http method, it won’t actually execute until the next $digest loop runs. Although most of the time we’ll be calling $http inside of an $apply block, we can also execute the method outside of the Angular digest loop. To execute an $http function outside of the $digest loop, we need to wrap it in an $apply block.

That will force the digest loop to run, and our promises will resolve as we expect them to.

$scope.$apply(function() {
$http({
method: 'GET',
url: '/api/dataFile.json'
});
});

Hope this much understanding help!

1
Aaron Cicali On

Angular's $digest alone isn't going to solve the requirement, in my opinion. What if the user moves their mouse, shouldn't that restart the idle timer?

What I recommend is making a directive that creates an event listener on the $window object for both keydown and mousemove. Then create a service that will allow other components to receive notification when the user's idle state changes.

Something like this:

angular
.module('myApp', [])
.service('Idle', function(){
  var self = {
  callbacks: {
   onidle: []
  },
  on: function(event, callback){
   event = 'on' + event.toLowerCase();
   if(self.callbacks[event] && typeof callback === 'function'){
    self.callbacks[event].push(callback);
   }
   return this;
  },
  trigger: function(event){
   event = 'on' + event.toLowerCase();
   if(self.callbacks[event])
   {
    lodash.each(self.callbacks[event], function(callback){
     callback.call();
    });
   }
   return this;
  }
  }
  
  return {
    on: self.on,
    trigger: self.trigger
  }
})
.directive('idleNotification',
function(
  $window,
  Idle
){
  return {
    link: function($scope){
      var timer = setTimer();

      angular
        .element($window)
        .on('keydown mousemove', function(){
          clearTimeout(timer);
          timer = setTimer();
        });
      
      function setTimer(){
        return setTimeout(function(){
          Idle.trigger('idle');
        }, 30);
      }
    }
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>

Then use the Idle service like so:

Idle.on('idle', function(){
    // do something here when the user becomes idle
});