A comprehensive analysis of the differences between $Apply of and $Digest of in Angular

  • 2021-07-09 06:38:30
  • OfStack

$apply () and $digest () are two core concepts in AngularJS, but sometimes they are confusing. To understand how AngularJS works, you first need to understand how $apply () and $digest () work. The purpose of this article is to explain what $apply () and $digest () are and how to apply them in everyday coding.

1. Explore $apply () and $digest ()

1.1. Understanding bidirectional data binding and $watch ();

AngularJS provides a very cool feature called bi-directional data binding (Two-way Data Binding), which greatly simplifies the way we write code. Data binding means that when any data in View changes, the changes are automatically fed back to the data in scope, which means that the scope model is automatically updated. Similarly, when the scope model changes, the data in view is updated to the latest value. So how does AngularJS do this? When you write down an expression such as {{aModel}}, AngularJS will set up an watcher for you behind the scenes on the scope model, which is used to update view when the data changes. The watcher here is the same as the watcher you will set in AngularJS:


$scope.$watch( ' aModel', function(newValue, oldValue) { 
//update the DOM with newValue 
});

The second argument passed into $watch () is a callback function that is called when the value of aModel changes. This callback function will be called to update view when aModel changes, which is understandable, but there is a very important problem! How does AngularJS know when to call this callback function? In other words, how did AngularJS know that aModel had changed before calling the corresponding callback function? Does it periodically run a function to check whether the data in the scope model has changed? Well, this is where the $digest loop comes into play.

In the $digest loop, watchers is triggered. When an watcher is triggered, AngularJS detects the scope model, and if it changes, the callback function associated with the watcher is called. So, the next question is when did the $digest loop start in various ways?

After $scope. $digest () is called, the $digest loop begins. Suppose you change a piece of data in scope in the handler function corresponding to an ng-click instruction, and AngularJS automatically triggers a $digest loop by calling $digest (). When the $digest loop starts, it triggers each watcher. These watchers check whether the current model value in the scope is different from the last calculated model value. If not, the corresponding callback function will be executed. The result of calling this function is that the content of the expression (such as {{aModel}}) in view is updated. In addition to the ng-click directives, there are 1 other built-in directives and services that allow you to change models (e.g. ng-model, $timeout, etc.) and automatically trigger a $digest loop.

Not bad so far! However, there is one small problem. In the above example, AngularJS does not call $digest () directly, but calls $scope. $apply (), which calls $rootScope. $digest (). Therefore, a $digest loop starts at $rootScope, and then watchers in all children scope is accessed.

Note: $scope. $apply () automatically calls $rootScope. $digest ().

The $apply () method takes two forms:

The first takes an function as a parameter, executes the function and triggers a $digest loop.

The second type does not accept any parameters, but triggers one round of $digest loop. We will soon see why the first form is better.

1.2 When do you call the $apply () method manually?

If AngularJS always starts a $digest loop by putting our code wrap into an function and passing it into $apply (), when do we need to call the $apply () method manually? In fact, AngularJS has a very specific requirement for this, that is, it is only responsible for automatically responding to changes that occur in the context of AngularJS (that is, changes to models that occur in the $apply () method). This is what the built-in directive of AngularJS does, so any changes to model are reflected in view. However, if you modify model anywhere outside the context of AngularJS, you need to notify AngularJS by manually calling $apply (). This is like telling AngularJS that you have modified some models, and hoping that AngularJS will trigger watchers for you to respond correctly.

For example, if you use setTimeout () in JavaScript to update an scope model, AngularJS has no way of knowing what you changed. In this case, it is your responsibility to call $apply () to trigger a $digest loop. Similarly, if you have a command to set an DOM event listener and modify some models in that listener, you also need to manually call $apply () to ensure that the changes are correctly reflected in view.

Let's look at an example. Add that you have 1 page, and once the page is loaded, you want to display 1 message after two seconds. Your implementation might look like this:

html:


<body ng-app= " myApp " > 
<div ng-controller= " MessageController " > 
Delayed Message: {{message}} 
</div> 
</body>

JavaScript:


/* What happens without an $apply() */ 
angular.module( ' myApp',[]).controller( ' MessageController', function($scope) { 
$scope.getMessage = function() { 
setTimeout(function() { 
$scope.message =  ' Fetched after 3 seconds'; 
console.log( ' message:'+$scope.message); 
}, 2000); 
} 
$scope.getMessage(); 
});

By running this example, you will see that after two seconds, the console does show the updated model, however, view is not updated. The reason you may already know is that we forgot to call the $apply () method. Therefore, we need to modify getMessage () as follows:


/* What happens with $apply */ 
angular.module( ' myApp',[]).controller( ' MessageController', function($scope) { 
$scope.getMessage = function() { 
setTimeout(function() { 
$scope.$apply(function() { 
//wrapped this within $apply 
$scope.message =  ' Fetched after 3 seconds'; 
console.log( ' message:' + $scope.message); 
}); 
}, 2000); 
} 
$scope.getMessage(); 
});

If you run the above example, you will see that view will be updated after two seconds. The only change is that our code is now wrapped into $scope. $apply (), which automatically triggers $rootScope. $digest (), so that watchers is triggered to update view.

Note: By the way, you should use $timeout service instead of setTimeout (), because the former will help you call $apply (), so you don't need to call it manually.

Also, note that in the above code you can also manually call $apply () without arguments after modifying model, as follows:


$scope.getMessage = function() { 
setTimeout(function() { 
$scope.message =  ' Fetched after two seconds'; 
console.log( ' message:' + $scope.message); 
$scope.$apply(); //this triggers a $digest 
}, 2000); 
};

The above code uses the second form of $apply (), that is, the form without parameters. Remember that you should always use the $apply () method that takes an function as an argument. This is because when you pass an function into $apply (), the function is wrapped in an try … catch block, so when an exception occurs, it is handled by $exceptionHandler service.

Using $apply () is as follows:

You can usually call $apply () by relying on any instruction provided by Angular that can be used in the view. All ng-[event] instructions (such as ng-click, ng-keypress) call $apply ().

You can also rely on the services built into Series 1 Angular to call $digest (). For example, the $http service calls $apply () after the XHR request completes and triggers an update return value (promise).

• Whenever we handle events manually, use a third-party framework (such as jQuery, Facebook API), or call setTimeout (), we can use the $apply () function to make Angular return a $digest loop.

Call setTimeout ():


<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>$scope.$apply() Usage </title>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div id="div1" ng-controller="mytext"> 
<div>{{text}}</div> 
<input id="btn" type="button" value="jquery-event"></input> 
</div> 
</body>
</html>
<script type="text/javascript">
var myModule = angular.module('myApp', []); 
myModule.controller("mytext",function($scope){ 
$scope.text = "place"; 
setTimeout(function(){ 
$scope.text = "value setted after time out"; 
$scope.$apply();// Manual dirty value detection is required , Otherwise, the data cannot be refreshed to the interface  
},1000); 
}); 
</script>

Using a third-party framework (e.g. jQuery, Facebook API):


<!DOCTYPE html>
<html ng-app="myApp">
<head>
<title>$scope.$apply() Usage </title>
<meta charset="utf-8">
<script src="https://cdn.jsdelivr.net/jquery/3.1.0/jquery.min.js"></script>
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script>
</head>
<body>
<div id="div1" ng-controller="mytext"> 
<div>{{text}}</div> 
<input id="btn" type="button" value="jquery-event"></input> 
</div> 
</body>
</html>
<script type="text/javascript">
var myModule = angular.module('myApp', []); 
myModule.controller("mytext",function($scope){ 
$scope.text = "place"; 
}); 
$(function(){ 
$("#btn").click(function(){ 
var $scope = $("#btn").scope(); 
$scope.text = "value setted in jquery"; 
$scope.$apply(); 
}); 
}) 
</script>

1.3. How many times does the $digest loop run?

When a $digest loop runs, watchers is executed to check whether models in scope has changed. If there is a change, the corresponding listener function will be executed. This involves an important issue. What if the listener function itself modifies an scope model? How will AngularJS handle this situation?

The answer is that the $digest loop will not run only once. At the end of the current loop, it executes another loop to check whether models has changed. This is the dirty check (Dirty Checking), which handles model changes that may occur when the listener function is executed. Therefore, the $digest loop continues until model is no longer changed, or the number of $digest loops reaches 10. Therefore, try not to modify model in the listener function as much as possible.

Note: The $digest loop runs at least twice, even though no model is changed in the listener function. As discussed above, it runs once more to ensure that the models is unchanged.

Conclusion

The most important thing to remember is whether AngularJS can detect your changes to model. If it cannot be detected, then you need to call $apply () manually.

If you have any questions, please leave me a message, and this site will reply to you in time. Thank you very much for your support to this site!


Related articles: