In-depth study of the two-way Data Binding Mechanism in AngularJS, and in-depth study of angularjs
Angular JS (Angular. JS) is a set of frameworks, templates, and data binding and rich UI components used to develop web pages. It supports the entire development process and provides a web application architecture without manual DOM operations. AngularJS is very small and only 60 kb. It is compatible with mainstream browsers and works well with jQuery. Two-way Data Binding may be the coolest and most practical feature of AngularJS, demonstrating the principles of MVC.
The working principle of AngularJS is that the HTML template will be parsed to the DOM by the browser, and the DOM structure will become the input of the AngularJS compiler. AngularJS will traverse the DOM template to generate corresponding NG commands. All commands are responsible for setting data binding for view (ng-model in HTML. Therefore, the NG framework starts to take effect after the DOM is loaded.
In html:
<body ng-app="ngApp"> <div ng-controller="ngCtl"> <label ng-model="myLabel"></label> <input type="text" ng-model="myInput" /> <button ng-model="myButton" ng-click="btnClicked"></button> </div></body>
In js:
// angular appvar app = angular.module("ngApp", [], function(){ console.log("ng-app : ngApp");});// angular controllerapp.controller("ngCtl", [ '$scope', function($scope){ console.log("ng-controller : ngCtl"); $scope.myLabel = "text for label"; $scope.myInput = "text for input"; $scope.btnClicked = function() { console.log("Label is " + $scope.myLabel); }}]);
As shown above, we first define an angular app in html and specify an angular controller, the controller corresponds to a scope (you can use the $ scope prefix to specify attributes and methods in the scope ). then, the value or operation of the HTML Tag within the scope of the ngCtl can be bound to the attributes and methods in js through $ scope.
In this way, NG's bidirectional data binding is implemented: that is, the view displayed in HTML is consistent with the data in AngularJS. If you modify one, the other end will change accordingly.
This method is really convenient to use. we only care about the HTML Tag style and its attributes and Methods bound under the angular controller scope in js. that's all. All the complicated DOM operations are omitted.
In fact, this idea is completely different from jQuery's DOM query and operations. Therefore, many people suggest using AngularJS instead of using jQuery together. of course, the two have their own advantages and disadvantages.
The app in NG is equivalent to a module. Multiple controllers can be defined in each app, and each controller has its own scope space without interfering with each other.
How does Binding data take effect?
AngularJS beginners may step on such a pitfall. assume there is a command:
var app = angular.module("test", []);app.directive("myclick", function() { return function (scope, element, attr) { element.on("click", function() { scope.counter++; }); };});app.controller("CounterCtrl", function($scope) { $scope.counter = 0;});<body ng-app="test"> <div ng-controller="CounterCtrl"> <button myclick>increase</button> <span ng-bind="counter"></span> </div></body>
At this time, click the button and the number on the interface will not be added. Many people may be confused, because he looked at the debugger and found that the data has indeed increased. Isn't Angular a two-way binding? Why is the data changed and the interface is not refreshed?
Try to add scope. digest () next to scope. counter ++?
Why? Under what circumstances? We found that there is no digest in the first example, And if you write digest, it will throw an exception saying that other digest is being done. What is the problem?
Let's first think about how to implement this function without AngularJS?
<! DOCTYPE html>
We can see that in this simple example, we have done some bidirectional binding. From the click of two buttons to the change of data, this is easy to understand, but we did not directly use the DOM onclick method, but instead made a ng-click, in bind, take out the function corresponding to ng-click and bind it to The onclick event processing function. Why? Because the data has changed, but it has not been filled on the interface, we need to do some additional operations here.
From another perspective, when data is changed, you need to apply the change to the interface, that is, the three spans. However, Angular uses dirty detection, which means that after the data is changed, you have to do something to trigger the dirty detection and then apply it to the corresponding DOM element of the data. The problem is how to trigger the dirty checking? When will it be triggered?
We know that some setter-based frameworks can assign values to the binding variables on DOM elements when setting values for data. The dirty detection mechanism does not have this stage. It does not have any way to be notified immediately after data changes. Therefore, you can only manually call apply () in each event entry (), apply data changes to the interface. In the real Angular implementation, dirty detection is performed first to confirm that the data has changed before setting a value for the interface.
Therefore, we encapsulate the real click in ng-click. The most important function is to append apply () and apply the data changes to the interface.
Why is an error reported when $ digest is called in ng-click? Because of Angular's design, only one $ digest can be run at a time, while the built-in commands such as ng-click have triggered $ digest. The current command has not been completed yet, so an error occurs.
$ Digest and $ apply
In Angular, there are two functions: $ apply and $ digest. We just used $ digest to apply this data to the interface. However, you can use $ apply instead of $ digest to achieve the same effect. What are their differences?
The most direct difference is that $ apply can include a parameter, which can accept a function, and then call this function after applying the data. Therefore, when integrating code from a non-Angular framework, you can write the code and call it in it.
var app = angular.module("test", []);app.directive("myclick", function() { return function (scope, element, attr) { element.on("click", function() { scope.counter++; scope.$apply(function() { scope.counter++; }); }); };});app.controller("CounterCtrl", function($scope) { $scope.counter = 0;});
Are there any other differences?
In a simple data model, there is no essential difference between the two, but it is different when there is a hierarchy. Considering that there are two levels of scopes, we can call these two functions in the parent scope or in the subscope. At this time, we can see the difference.
For $ digest, the call in the parent scope and sub-scope is different, but for $ apply, the two are the same. Let's construct a special example:
Var app = angular. module ("test", []); app. directive ("increasea", function () {return function (scope, element, attr) {element. on ("click", function () {scope. a ++; scope. $ digest () ;};};}); app. directive ("increaseb", function () {return function (scope, element, attr) {element. on ("click", function () {scope. B ++; scope. $ digest (); // replace this with $ apply.}) ;}}; app. controller ("OuterCtrl", ["$ scope", function ($ scope) {$ scope. a = 1; $ scope. $ watch ("a", function (newVal) {console. log ("a:" + newVal) ;}); $ scope. $ on ("test", function (evt) {$ scope. a ++;}) ;}]); app. controller ("InnerCtrl", ["$ scope", function ($ scope) {$ scope. B = 2; $ scope. $ watch ("B", function (newVal) {console. log ("B:" + newVal); $ scope. $ emit ("test", newVal) ;}) ;}]); <div ng-app = "test"> <div ng-controller = "OuterCtrl"> <div ng-controller = "InnerCtrl"> <button increaseb> increase B </button> <span ng-bind = "B"> </span> </div> <button increasea> increase a </button> <span ng-bind = "a"> </ span> </div>
At this time, we can see the difference. When you click the increase bbutton, the values of a and B have actually changed, but a is not updated on the interface, it will not be updated until you click "increase. How can this problem be solved? In the implementation of the increaseb command, replace $ digest with $ apply.
When $ digest is called, only monitoring on the current scope and its subscopes is triggered, but when $ apply is called, all monitoring on the scope tree is triggered.
Therefore, in terms of performance, if you can determine the impact scope of your own data changes, you should try to call $ digest, $ apply is used only when the scope of impact caused by data changes cannot be accurately identified. It is very violent to traverse the entire scope tree and call all the monitors.
From another perspective, we can also see why we recommend that you put $ apply when calling an external framework, because this is the only place where all data changes are applied, if $ digest is used, temporary data changes may be lost.
Advantages and disadvantages of dirty Detection
Many people are disdainat Angular's dirty detection mechanism and advocate the observation mechanism based on setter and getter. In my opinion, this is just a different implementation method of the same thing, no one is better than anyone. The two have their own advantages and disadvantages.
We all know that DocumentFragment is recommended when DOM elements are added in batches in a loop. Why? If DOM is changed every time, the structure of the DOM tree must be modified, the performance has a big impact. If we can first create the DOM structure in the document fragments and add it to the main document as a whole, the changes to the DOM tree will be completed once, and the performance will be improved a lot.
Similarly, in the Angular framework, consider the following scenarios:
function TestCtrl($scope) { $scope.numOfCheckedItems = 0; var list = []; for (var i=0; i<10000; i++) { list.push({ index: i, checked: false }); } $scope.list = list; $scope.toggleChecked = function(flag) { for (var i=0; i<list.length; i++) { list[i].checked = flag; $scope.numOfCheckedItems++; } };}
What if a text on the interface is bound to this numOfCheckedItems? Under the dirty detection mechanism, this process is not under pressure. All data changes are completed at a time and then applied to the interface as a whole. At this time, the setter-based mechanism will be miserable, unless it also delays batch operations to an update like Angular, otherwise the performance will be lower.
Therefore, the two monitoring methods have their own advantages and disadvantages. The best way is to understand the differences in their use methods and consider the differences in their performance. In different business scenarios, avoid the usage that is most likely to cause performance bottlenecks.
Articles you may be interested in:
- Details about the scope and data binding in the JavaScript AngularJS framework
- AngularJS single-choice and multi-choice boxes implement bidirectional dynamic binding
- Understanding Angular Data two-way binding
- Two-way binding between custom Angular commands and jQuery-implemented Bootstrap-style data single-choice and multi-choice drop-down boxes
- About how to implement bidirectional binding in angular. js $ watch $ digest $ apply
- Angularjs learning notes-two-way Data Binding
- Example of one-way binding using NG-BIND COMMANDS IN ANGULARJS
- Instance analysis: bidirectional data binding in AngularJS framework