$apply and $digest in Action

To see how $apply() is used in real world, let’s create a very simple app that schedules a function to run after three seconds. This function simply updates a scope model and the view updates itself with the new model value. To do this we’ll make use of setTimeout(). Here’s the code:

View

<body ng-controller="TimeoutController">
  <button ng-click="scheduleTask()">Get Message after 3 seconds
↵</button>
  <br/>Message fetched: {{message}}
</body>

Controller

angular.module('myApp',[]).controller('TimeoutController', 
↵function($scope) {

  $scope.scheduleTask = function() {
    setTimeout(function() {
      $scope.message = 'Fetched after 3 seconds';
      console.log('message='+$scope.message); //log this to console
    }, 3000);
  }

});

So, when you click on the button in the view, $scope.scheduleTask() runs which, in turn, uses setTimeout to run a function after a three-second delay. This function, when executed, updates our $scope.messgae model.

But if you run the above snippet and click on the button you won’t see the view update itself after three seconds. However, in the console you can find the logged message. So, we’re sure that the function was called and updated our model. But the view didn’t update itself. The reason is that—and here’s the ‘aha moment’— we didn’t wrap our code inside $apply(). So, you need to make the following modification in the code to get it working:

$scope.scheduleTask = function() {
  setTimeout(function() {
    $scope.$apply(function() { // wrapped the code in $apply()
      $scope.message = 'Fetched after 3 seconds'; 
↵//will reflect in view
      console.log('message=' + $scope.message);
    });
  }, 3000);
}

As you can see we have wrapped our code inside $apply() so that a $digest() cycle starts, the watchers are fired and our view is updated. Now if you click the button the delayed function runs and view displays the message as expected.