Home>

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

1.1 Recognize two-way data binding and $watch ();

angularjs provides a very cool feature called two-way data binding, which greatly simplifies the way we write code.Data binding means that when any data in the view changes,Then this change will be automatically fed back to the scope data,This means that the scope model will be updated automatically.Similarly, when the scope model changes,The data in the view is also updated to the latest value.So how does angularjs do this?When you write an expression like {{amodel}}, angularjs will set up a watcher on the scope model for you behind the scenes, which is used to update the view when the data changes. The watcher here is the same as the watcher you would set up in angularjs:

$scope. $watch ("amodel", function (newvalue, oldvalue) {
//update the dom with newvalue
});

The second parameter passed to $watch () is a callback function,This function is called when the value of amodel changes.When amodel changes,It's easy to understand that this callback function will be called to update the view.However, there is an important issue! How does angularjs know when to call this callback function?in other words,How does angularjs know that amodel has changed,Did you call the corresponding callback function?Does it periodically run a function to check if the data in the scope model has changed?Well, this is where the $digest loop comes in.

In the $digest loop, watchers are triggered.When a watcher is triggered,AngularJS will detect the scope model. If it changes, then the callback function associated with the watcher will be called.So, the next question is when did the $digest loop start in various ways?

After calling $scope. $Digest (), the $digest loop begins.Suppose you change a piece of data in the scope in the handler function corresponding to an ng-click instruction.At this point, angularjs will automatically trigger a $digest loop by calling $digest (). When the $digest loop starts,It will trigger every watcher. These watchers will check if the current model value in the scope is different from the model value calculated last time. If different,Then the corresponding callback function will be executed.The result of calling the function,Is the content of the expression in the view (translation:such as {{amodel}}) will be updated.In addition to the ng-click directive, there are other built-in directives and services that let you change models (such as ng-model, $timeout, etc.) and automatically trigger a $digest loop.

So far so good! However, there is a small problem.In the example above,angularjs does not call $digest () directly, but instead calls $scope. $apply (), which will call $rootscope. $digest (). Therefore, a round of $digest loop starts at $rootscope, and then it will access all the watches in the children scope.

note:$scope. $apply () will automatically call $rootscope. $digest ().

The $apply () method has two forms:

The first takes a function as a parameter,This function is executed and a $digest cycle is triggered.

The second one does not accept any parameters,Just trigger a round of $digest loop. We will see immediately why the first form is better.

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

If angularjs always wraps our code into a function and passes it to $apply () to start a $digest cycle, when will we need to manually call the $apply () method?In fact, angularjs has very clear requirements for this,That is, it is only responsible for automatically responding to changes that occur in the angularjs context (that is, changes to models that occur in the $apply () method). angularjs's built-in instruction does just that,So any model changes will be reflected in the view. However, if you modify the model anywhere outside the angularjs context, then you need to notify angularjs by manually calling $apply (). This is like telling angularjs that you have modified some models and hope that angularjs will help you trigger watchers to make the correct response.

Let's look at an example.Join you have a page,Once the page is loaded,You want a message to appear 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 after two seconds,The console does show the updated model, however, the view is not updated.Maybe you already know why,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 above example,You will see that the view will update in two seconds.The only change is that our code is now wrapped into $scope. $Apply (), which will automatically trigger $rootscope. $Digest (), so that the watchers are triggered to update the 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 () with no parameters after modifying the model, like this:

$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, no parameter form.Keep in mind that you should always use the $apply () method that accepts a function as a parameter. This is because when you pass a function into $apply (),This function will be wrapped in a try ... catch block, so once an exception occurs,The exception will be handled by the $exceptionhandler service.

The use of $apply () is as follows:

You can usually call $apply () on any directive provided by angular that can be used in a view. All ng- [event] directives (such as ng-click, ng-keypress) will call $apply ().

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 completes and triggers the update return (promise).

•Whenever we handle events manually,Using a third-party framework (such as jquery, facebook api), or calling settimeout (), you can use the $apply () function to make angular return a $digest loop.

Calling 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 ng-controller="mytext">
<div>{{text}}</div>
<input 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 a third-party framework (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>
</head>
<body>
<div ng-controller="mytext">
<div>{{text}}</div>
<input 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 are executed to check if the models in the scope have changed.If something changes,Then the corresponding listener function will be executed.This involves an important issue.What if the listener function itself would modify a scope model?How does angularjs handle this situation?

The answer is that the $digest loop does not run only once.After the end of the current cycle,It will perform another loop to check if any models have changed.This is dirty checking, which is used to deal with model changes that may be caused when the listener function is executed. Therefore, the $digest loop will continue to run until the model no longer changes,Or the number of $digest loops reached 10 times. Therefore, whenever possible, do not modify the model in the listener function.

note:The $digest loop will also run at least twice,Even in the listener function, no model is changed. As discussed above,It will run one more time to ensure that the models have not changed.

Conclusion

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

  • Previous Python automated test Eclipse + Pydev build development environment
  • Next JAVA immutable mechanism and immutability of String (recommended)