This article details: How to implement the two-way binding of variables on $scope objects by dirty checking at 1.angular. 2. Three important ways to achieve angular two-way binding: $digest (), $apply (), $watch ().
Angular unlike Ember.js, which implements two-way binding by dynamically setting setter functions and Getter functions, dirty checks allow angular to listen for variables that may not exist.
$scope. $watch syntax sugar: $scope. $watch (watchexp,listener,objectequality);
When listening for a variable, you need to call $scope. $watch function, this function accepts three parameters: the value to be detected or the expression (WATCHEXP), the listener function, the value changes when the execution (listener anonymous function), whether to open the value detection, for True detects internal changes to an object or array (that is, choosing to compare or angular.equals in the same way as = = =). As an example:
1 $scope. Name = ' Ryan '; 2 3 function () {4 return $scope. Name; 5 function (NewValue, oldValue) {6 console.log (' $scope. Name was updated! ' ); 7 });
Angular will register your listener function listener on the $scope object, you can note that there is a log output "$scope. Name was updated!" because $scope.name was updated by the previous undefined to ' Ryan '. Of course, watcher can also be a string, the effect is the same as the anonymous function in the above example, in the angular source code,
1 if (typeof Watchexp = = ' String ' &&get.constant) {2var originalfn = Watcher.fn; 3 function (newval, Oldval, scope) {4 originalfn.call (This, newval, oldval, scope); 5 arrayremove (array, watcher); 6 }; 7 }
The above code sets Watchexp to a function that invokes the listener function with the given variable name.
Here is an example of an application, with an interpolation of {{post.title}}, when angular encounters this syntax element during the compile compilation phase, the internal processing logic is as follows:
function (AST) { var node = document.createTextNode (""); This function (newval) { null? "": "" + newval)); }) return node;}
This code is well understood, that is, when an interpolation is encountered, a new textnode is created, and the value is written to the nodecontent. So how does angular determine if this node value changes or adds a new node?
You have to mention the $digest function here. First, through the $watch interface, a listening queue of $ $watchers is generated. The $ $watchers object under the $scope object has all of the watchers you define. If you go inside the $ $watchers, you'll find an array of it.
$ $watchers =[{eq:false,//Whether or not we is checking for objectequalitywhether you need to determine the equality of the object levelFn:function(NewValue, OldValue) {},//This is the listener function we ' ve providedThis is the listener function we provideLast: ' Ryan ',//The last known value for the variable$nbsp; $nbsp;the latest value of the variableExp:function(){},//This is the watchexp function we provided$nbsp; $nbsp;the WATCHEXP function we provideGetfunction(){}//Angular ' s compiled watchexp function angualr compiled watchexp functions }];
$watch function returns a Deregisterwatch functions, which means that if we use scope. $watch to monitor A variable, you can also stop listening by calling Deregisterwatch.
I'm the Moe-click line.
In Angularjs, when a controller/directive/etc is running, the angular internally runs $scope. $apply () function, which takes a parameter, the argument is a function FN, This function is used to execute the FN function and will not run $scope in the $rootscope scope until after the FN is executed. $digest this function. Angular in the source code, this describes $apply this function.
$apply:function(expr) {Try{beginphase (' $apply '); Try { return This. $eval (expr); } finally{clearphase (); } } Catch(e) {$exceptionHandler (e); } finally { Try{$rootScope. $digest (); } Catch(e) {$exceptionHandler (e); Throwe; } } }
The above-mentioned expr parameter is actually a function that either you or angular is adjusting the scope. $apply This function is passed in. But most of the time you may not be using this function, and remember to pass it on to him.
OK, say so much, let's see how angular things use $scope. $apply, the following ng-keydown this directive to illustrate, in order to register this directive, and see how the source code is stated:
varNgdirectives ={};foreach (' Click DblClick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress Submit Focus B Lur copy cut Paste '. Split (', '),function(){ varDirectivename = directivenormalize (' ng-' +name); Ngeventdirectives[directivename]= [' $parse ',function($parse) {return{compile:function($element, attr) {varfn =$parse (Attr[directivename]); return functionNgeventhandler (scope, Element) {Element.on (lowercase (name),function(event) {scope. $apply (function() {fn (scope, {$event: event}); }); }); }; } }; }];});
The above code iterates through various different types of events that may be triggered and creates a call Ng-[eventnamehere] (the event name in brackets), in this directive compile function, it registers an event handler on the element, The event corresponds to the corresponding directive name one by one, for example, the Cilck event and the Ng-click directive. When the Click event is triggered (or if the ng-click instruction is triggered), angular executes the scope. $apply, execute the parameters in the $apply (parameter is function).
The above code simply changes the value in the $scope associated with the element (Elment). This is a one-way binding. This is also the reason that this instruction is called Ng-keydown, and it is only possible to give us a new value when the KeyDown event is triggered. Not that angular implemented two-way data binding?!
Take a look at Ng-model this directive, when you're using Ng-model, you can use bidirectional data binding – that's exactly what we want. Angularjs uses $scope. $watch (View-to-model) and $scope. $apply (Model-to-view) to implement this functionality.
Ng-model will bind the event processing instructions (for example, KeyDown) to the input elements we use-this is $scope. $apply be called! and $scope. $watch is called in the controller of the instruction. You can see this in the following code:
$scope. $watch (functionNgmodelwatch () {//Get the $scope object in Ngmodelcontroller, i.e. the data model;
varValue =Ngmodelget ($scope); //if the scope model value and the Ngmodel value are not synchronized, $modelValue the value that is bound to the model, value is the true value of the data model, $viewValue the value shown in the view. Ngmodel.ngmodelcontroller. $gormatters property is to format or convert the data model in the Ngmodel controller, $render functions need to be called when $modelvalue and $viewvalue are unequal. if(Ctrl. $modelValue!==value) { varformatters =Ctrl. $formatters, IDX=formatters.length; Ctrl. $modelValue=value; while(idx--) {Value=Formatters[idx] (value); } if(Ctrl. $viewValue!==value) {Ctrl. $viewValue=value; Ctrl. $render (); } } returnvalue;});
If you call $scope. $watch, only one parameter is passed for it, and the function is called regardless of what has changed in the scope. In Ng-model, this function is used to check that the model and view are not synchronized, and if there is no synchronization, it will update the model data with the new value. This function will return a new value, and when it runs in the $digest function, we will know what this value is!
So why does our listener sometimes not trigger or say it doesn't work?
As mentioned earlier, ANGULARJS will run $scope in the controller function of each instruction. $apply. If we look at the code of the $scope. $apply function, we will find that it will only run the $digest function after the controller function has started to be called – which means that if we stop listening immediately, $scope. $watch function is not even called! So when the $scope. $apply run, the $digest will also run, it will iterate through the $ $watchers, as long as the watchexp and the latest values are found to be unequal, the change triggers the event listener. In Angularjs, as long as the value of a model can change, $scope. $apply will run. That's why when you update $scope outside of Angularjs, for example in a settimeout function, you need to run $scope manually. $apply (): This allows the ANGULARJS to realize that its scope has changed.
But how does the digest process work? (below carefully explore the source code in the $digest function execution flow, can not look ... )
1. First, Mark Dirty = false;
2. Traverse the Listener object (current.$ $watchers) in the current scope, and by judging whether the value Watch.get (current) and the old value watch.last are equal in the currently listening object array: If not equal, set the tag dirty to True, Assigns the previous listener object Lastdirtywatch to the current listener, assigns the old value of the listener to the new value Watch.last, and finally calls the listener function Wantch.fn of the Watch object binding.
Traversescopesloop: Do{//"traverse the scopes" loop if((Watchers =current.$ $watchers)) { //Process Our watchesLength =watchers.length; while(length--) { Try{Watch=Watchers[length]; //Most common watches is on primitives, in which case we can short //circuit it with = = = operator, only when = = = = Fails do we use. Equals if(watch) {if(value = Watch.get (current))!== (last = watch.last) &&!(Watch.eq?equals (value, last): (typeofValue = = ' Number ' &&typeoflast = = = ' Number ' && IsNaN (value) &&IsNaN (last)))) {Dirty=true; Lastdirtywatch=Watch; Watch.last= Watch.eq? Copy (value,NULL): value; Watch.fn (Value, (last= = = Initwatchval)?value:last), current); if(TTL < 5) {Logidx= 4-TTL; if(!watchlog[logidx]) watchlog[logidx] = []; Watchlog[logidx].push ({msg:isfunction (watch.exp)? ' fn: ' + (Watch.exp.name | |watch.exp.toString ()): Watch.exp, Newval:value, Oldval:last }); } } Else if(Watch = = =Lastdirtywatch) { //If The most recently dirty watcher are now clean, short circuit since the remaining watchers //There are already been tested.Dirty =false; BreakTraversescopesloop; } } } Catch(e) {$exceptionHandler (e); } } }
3. Go to the next watch check, traverse the check round, and if dirty===true
we re-enter step 1. Otherwise go to step 4.
4. Complete the dirty check.
Finally, express the individual's view of this piece. As a beginner, there is no need to understand how he is going to implement data-bound in two directions. As long as you know that he has done it through a dirty check, you need to take the initiative to trigger some events. To enter $digest cycle:
To meet:
- DOM events, such as user input text, click Buttons, and so on. (Ng-click)
- XHR Response Event ($http)
- Browser location Change event ($location)
- Timer event ($timeout, $interval)
- Execute $digest () or $apply ()
So far, saying a lot of things you don't need to know, the next chapter won't be so nonsense.
Angular two-way data binding (bottom)