$apply() and $digest() are two core concepts in AngularJS, but sometimes they are confusing. In order to understand how AngularJS works, you first need to understand how $apply() and $digest() work. This article aims to explain what $apply() and $digest() are, and how they are applied in everyday encoding.
1. Explore $apply() and $digest()
1.1. Understand two-way data binding and $watch();
AngularJS provides a very cool feature called Two-way Data Binding, which greatly simplifies how we write our code. Data binding means that when any data in the View changes, the change will be automatically fed back to the scope data, which means that the scope model will be automatically updated. Similarly, when the scope model changes, the data in the view is updated to the latest value. So how does AngularJS do this? When you write an expression like {{ aModel }}, AngularJS will set a watcher for you on the scope model, which is used to update the 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 parameter passed into $watch() is a callback function, which will be called when the value of aModel changes. When aModel changes, it is not difficult to understand that this callback function will be called to update the view, but there is still a very important problem! How does AngularJS know when to call this callback function? In other words, how did AngularJS call the corresponding callback function when it knows that aModel has changed? Will it run a function periodically to check whether the data in the scope model has changed? Well, that's where the $digest loop comes in.
In the $digest loop, watchers will be fired. When a watcher is triggered, AngularJS will detect the scope model. If it changes, the callback function associated with the watcher will be called. So, the next question is when does the $digest loop start in various ways?
After calling $scope.$digest(), the $digest loop begins. Suppose you change a data in scope in the handler function corresponding to an ng-click directive, AngularJS will automatically trigger a $digest loop by calling $digest(). When the $digest loop starts, it triggers each watcher. These watchers will check whether the current model value in scope is different from the model value calculated last time. If it is different, the corresponding callback function will be executed. The result of calling this function is that the content of the expression in the view (translator's note: such as {{ aModel }}) will be updated. In addition to the ng-click directive, there are some other built-in directives and services to let you change models (such as ng-model, $timeout, etc.) and automatically trigger a $digest loop.
So far it's not bad! However, there is a 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, which will then access all children scope watchers.
Note: $scope.$apply() will automatically call $rootScope.$digest().
The $apply() method has two forms:
The first one will accept a function as a parameter, execute the function and trigger a $digest loop.
The second type will not accept any parameters and just trigger a $digest loop. We'll see right away why the first form is better.
1.2. When to manually call the $apply() method?
If AngularJS always wrap our code into a function and pass in $apply() to start a $digest loop, then when do we need to call the $apply() method manually? In fact, AngularJS has a very clear requirement for this, that it is only responsible for automatically responding to changes that occur in the AngularJS context (i.e., changes to models that occur in the $apply() method). This is how AngularJS's built-in directive does it, so any model changes will be reflected in the view. However, if you modify the model anywhere outside the AngularJS context, you need to notify AngularJS by calling $apply() manually. It's like telling AngularJS that you have modified some models and hope that AngularJS can help you trigger watchers to respond correctly.
For example, if you use setTimeout() in JavaScript to update a scope model, then AngularJS has no way of knowing what you have changed. In this case, it is your responsibility to call $apply(), and trigger a $digest loop by calling it. Similarly, if you have a directive to set a DOM event listener and modify some models in that listener, you also need to call $apply() manually to ensure that the changes will be correctly reflected in the view.
Let's look at an example. Join you have a page that once the page is loaded, you want to display a 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, the view is not updated. Maybe you already know the reason, that is, 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 example above, you will see that the view will also be updated after two seconds. The only change is that our code is now wrapped into $scope.$apply(), which will automatically trigger $rootScope.$digest(), so that watchers are triggered to update the view.
Note: By the way, you should use $timeout service instead of setTimeout(), because the former will call $apply() for you, so you don't need to call it manually.
Also, note that in the above code, you can also manually call $apply() without parameters after modifying the model, just like the following:
$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. It is important to remember that you should always use the $apply() method that takes a function as a parameter. This is because when you pass a function into $apply(), the function will be wrapped into a try...catch block, so once an exception occurs, the exception will be processed by the $exceptionHandler service.
The situation of using $apply() is as follows:
•You can usually call $apply() based on any directive provided by Angular that can be used in the view. All ng-[event] directives (such as ng-click, ng-keypress) will call $apply().
•In addition, you can also rely on a series of Angular built-in services to call $digest(). For example, the $http service will call $apply() after the XHR request is completed and the update return value is triggered.
•Whenever we manually handle events, use third-party frameworks (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();//Dirty value detection must be performed manually, otherwise the data cannot be refreshed to the interface},1000); }); </script>Use third-party frameworks (such as 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><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 will the $digest loop run?
When a $digest loop is running, watchers will be executed to check whether the models in scope have changed. If a change occurs, the corresponding listener function will be executed. This involves an important issue. What if the listener function itself will modify a scope model? How will AngularJS handle this situation?
The answer is that the $digest loop won't run only once. After the current loop ends, it will execute another loop to check whether models have changed. This is Dirty Checking, which is used to handle model changes that may occur when the listener function is executed. Therefore, the $digest loop will continue to run until the model no longer changes, or the $digest loop reaches 10 times. Therefore, try not to modify the model in the listener function as much as possible.
Note: The $digest loop will also run at least twice, even if no model is changed in the listener function. As discussed above, it will run once more to ensure that the models are not changing.
Conclusion
The most important thing to remember is whether AngularJS can detect your modifications to the model. If it cannot be detected, then you need to call $apply() manually.
If you have any questions, please leave me a message and the editor will reply to everyone in time. Thank you very much for your support to Wulin.com website!