Instance analysis: bidirectional data binding in AngularJS framework

Source: Internet
Author: User
This article mainly introduces two-way data binding application instances in the AngularJS framework, including explanations on key functions in Data Binding and listener triggering. For more information, see data binding.

By binding a text input box to the person. name attribute, we can make our application more interesting. This step creates a two-way binding between the text input box and the page.

In this context, "two-way" means that if the view changes the attribute value, the model will "see" the change. If the model changes the attribute value, the view will also "see" the change. Angular. js automatically sets up this mechanism for you. If you are curious about how this is implemented, please refer to a later article, which discusses the operation of digest_loop in depth.

To create this binding, we use the ng-model command attribute in the text input box, as shown in the following code:

Hello {{ person.name }}

Now we have created a data binding (that's right, it's so easy). Let's see how the view changes the model:

Try:

When you enter the name in the text box, the following name automatically changes, which shows the direction of Data Binding: From view to model.

We can also change the model in the background of our (client) to see that the change is automatically displayed on the front end. To demonstrate this process, let's write a timer function in the MyController model to update a data on $ scope. In the following code, we will create this timer function, which will time every second (like a clock) and update the clock variable data on $ scope:

app.controller('MyController', function($scope) { $scope.person = { name: "Ari Lerner" }; var updateClock = function() {  $scope.clock = new Date(); }; var timer = setInterval(function() {  $scope.$apply(updateClock); }, 1000); updateClock();});


We can see that when we change the data of the clock variable in the model, the view will be automatically updated to reflect this change. With braces, we can easily display the value of the clock variable in the view:

{{ clock }}



Interaction

Previously, we bound the data to the text input box. Please note that data binding is not only limited to data. We can also use binding to call functions in $ scope (as mentioned earlier ).

For buttons, links, or any other DOM elements, we can use another directive attribute to bind: ng-click. This ng-click Command binds the mouse click event (that is, the mousedown browser event) of the DOM element to a method. When the browser triggers the mouse click event on the DOM element, the bound method is called. Similar to the previous example, the bound code is as follows:

The simplest adding machine ever Add Subtract Current count: {{ counter }}


Both buttons and links are bound to all $ scope objects of the controller containing their DOM elements. Angular calls corresponding methods when they are clicked. Note that when we tell Angular what method to call, we write the method name into a string with quotation marks.

app.controller('DemoController', function($scope) { $scope.counter = 0; $scope.add = function(amount) { $scope.counter += amount; }; $scope.subtract = function(amount) { $scope.counter -= amount; };});

$ Scope. $ watch

$scope.$watch( watchExp, listener, objectEquality );


To monitor the changes of a variable, you can use the $ scope. $ watch function. This function has three parameters, which indicate "what to observe" (watchExp) and "what to happen when it changes" (listener ), and whether you want to monitor a variable or an object. When we check a parameter, we can ignore the third parameter. For example:

$scope.name = 'Ryan'; $scope.$watch( function( ) {  return $scope.name;}, function( newValue, oldValue ) {  console.log('$scope.name was updated!');} );


AngularJS will register your monitoring function in $ scope. You can output $ scope in the console to view registered projects in $ scope.

You can see in the console that $ scope. name has changed-this is because the value before $ scope. name seems undefined, and now we assign it to Ryan!

You can also use a string for the first parameter of $ wach. This is exactly the same as providing a function. In AngularJS source code, you can see that if you use a string, the following code will be run:

if (typeof watchExp == 'string' && get.constant) { var originalFn = watcher.fn; watcher.fn = function(newVal, oldVal, scope) {  originalFn.call(this, newVal, oldVal, scope);  arrayRemove(array, watcher); };}


This will set our watchExp as a function, and it will automatically return the variable in the scope that we have already set the name.

$ Watchers
The $ watchers variable in $ scope stores all our defined monitors. If you view $ watchers in the console, you will find that it is an array of objects.

$ Watchers = [{eq: false, // indicates whether we need to check whether the object level is equal to fn: function (newValue, oldValue ){}, // This is our listener function last: 'ry', // The latest value of the variable exp: function () {}, // The watchExp function get: function () We provide () {}// Angular's compiled watchExp function}];


The $ watch function returns a deregisterWatch function. This means that if we use $ scope. $ watch to monitor a variable, we can also stop monitoring by calling a function later.

$ Scope. $ apply
When a controller, command, or other object runs in AngularJS, AngularJS runs a function called $ scope. $ apply. The $ apply function receives a function as a parameter and runs it. After that, it runs the $ digest function on rootScope.

The $ apply Function Code of AngularJS is as follows:

   $apply: function(expr) {  try {   beginPhase('$apply');   return this.$eval(expr);  } catch (e) {   $exceptionHandler(e);  } finally {   clearPhase();   try {    $rootScope.$digest();   } catch (e) {    $exceptionHandler(e);    throw e;   }  }}


The expr parameter in the above Code is that you are calling $ scope. $ apply () is a parameter passed. However, you may not use $ apply in most cases. Remember to pass a parameter to $ apply.

Next let's take a look at how ng-keydown uses $ scope. $ apply. To register this command, AngularJS uses the following code.

var ngEventDirectives = {};forEach( 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), function(name) {  var directiveName = directiveNormalize('ng-' + name);  ngEventDirectives[directiveName] = ['$parse', function($parse) {   return {    compile: function($element, attr) {     var fn = $parse(attr[directiveName]);     return function ngEventHandler(scope, element) {      element.on(lowercase(name), function(event) {       scope.$apply(function() {        fn(scope, {$event:event});       });      });     };    }   };  }]; });


The above code loops through different types of events, which may be triggered later and create a new instruction called ng-[an event. In the compile function of the instruction, it registers an event processor on the element, which corresponds to the instruction name one by one. When an event is triggered, AngularJS runs the scope. $ apply function and allows it to run a function.

Is it just one-way data binding?
The ng-keydown mentioned above can only change the value in $ scope associated with the element value-this is only a single data binding. This is also the reason why this command is called ng-keydown. It can only give us a new value when the keydown event is triggered.

But what we want is two-way data binding!
Let's take a look at ng-model. When you are using ng-model, you can use two-way data binding-this is exactly what we want. AngularJS uses $ scope. $ watch (view-to-model) and $ scope. $ apply (model-to-view) to implement this function.

Ng-model binds event processing commands (such as keydown) to the input elements we use-this is where $ scope. $ apply is called! $ Scope. $ watch is called in the instruction controller. You can see this in the following code:

$ Scope. $ watch (function ngModelWatch () {var value = ngModelGet ($ scope); // if the scope model value and ngModel value are not synchronized if (ctrl. $ modelValue! = Value) {var formatters = ctrl. $ formatters, idx = formatters. length; ctrl. $ modelValue = value; while (idx --) {value = formatters [idx] (value);} if (ctrl. $ viewValue! = Value) {ctrl. $ viewValue = value; ctrl. $ render () ;}} return value ;});


If you pass only one parameter to $ scope. $ watch, this function will be called no matter what changes in the scope. In ng-model, this function is used to check whether the model and view are synchronized. If not, it uses a new value to update the model data. This function returns a new value. When it runs in the $ digest function, we will know what this value is!

Why is our listener not triggered?
If we stop this listener in the $ scope. $ watch listener function, even if we update $ scope. name, this listener will not be triggered.

As mentioned above, AngularJS will run $ scope. $ apply in the controller function of each instruction. If we view $ scope. $ apply function code, we will find that it will only run $ digest function after the controller function has been called-this means that if we stop listening immediately, $ scope. $ watch functions won't even be called! But how does it run?

$ Digest function will be called by $ scope. $ apply in $ rootScope. It runs the digest loop in $ rootScope, then traverses each scope and runs the loop in each scope. In a simple case, the digest loop will trigger all watchExp functions in the $ watchers variable and compare them with the latest values. If the values are different, the listener is triggered.

When digest runs cyclically, it will traverse all the listeners and then loop again. As long as "Dirty value" is found in this loop, the loop will continue. If the value of watchExp is different from the latest value, this cycle will be considered as a dirty value. Ideally, it runs once. If it runs more than 10 times, you will see an error.

Therefore, when $ scope. $ digest also runs when apply is running. It cyclically traverses $ watchers. If the value of watchExp is different from the latest value, the event listener is triggered when the change occurs. In AngularJS, $ scope. $ apply runs as long as the value of a model may change. This is why when you update $ scope outside AngularJS, for example, in a setTimeout function, you need to manually run $ scope. $ apply (): This makes AngularJS aware that its scope has changed.

Create your own dirty checking
So far, we can create a small, simplified version of dirty value check. Of course, the dirty checking implemented in AngularJS is more advanced. It provides crazy asynchronous queues and other advanced functions.

Set Scope
Scope is just a function that contains any object we want to store. We can extend the prototype object of this function to copy $ digest and $ watch. We don't need the $ apply method, because we don't need to execute any function in the context of the scope-we just need to simply use $ digest. The code of our Scope is as follows:

var Scope = function( ) {  this.$$watchers = []; }; Scope.prototype.$watch = function( ) { }; Scope.prototype.$digest = function( ) { };


Our $ watch function requires two parameters: watchExp and listener. When $ watch is called, we need to push them into the $ watcher array of Scope.

var Scope = function( ) {  this.$$watchers = []; }; Scope.prototype.$watch = function( watchExp, listener ) {  this.$$watchers.push( {    watchExp: watchExp,    listener: listener || function() {}  } );}; Scope.prototype.$digest = function( ) { };


You may have noticed that if listener is not provided, we will set listener as an empty function-so that we can $ watch all the variables.

Next, we will create $ digest. We need to check whether the old value is equal to the new value. If the two are not equal, the listener will be triggered. We will keep repeating this process until the two are equal. This is the source of "Dirty value"-the dirty value means that the new value and the old value are not equal!

var Scope = function( ) {  this.$$watchers = []; }; Scope.prototype.$watch = function( watchExp, listener ) {  this.$$watchers.push( {    watchExp: watchExp,    listener: listener || function() {}  } );}; Scope.prototype.$digest = function( ) {  var dirty;   do {      dirty = false;       for( var i = 0; i < this.$$watchers.length; i++ ) {        var newValue = this.$$watchers[i].watchExp(),          oldValue = this.$$watchers[i].last;         if( oldValue !== newValue ) {          this.$$watchers[i].listener(newValue, oldValue);           dirty = true;           this.$$watchers[i].last = newValue;        }      }  } while(dirty);};


Next, we will create an instance with a scope. We assign this instance to $ scope. We will register a listener function and run $ digest after updating $ scope!

var Scope = function( ) {  this.$$watchers = []; }; Scope.prototype.$watch = function( watchExp, listener ) {  this.$$watchers.push( {    watchExp: watchExp,    listener: listener || function() {}  } );}; Scope.prototype.$digest = function( ) {  var dirty;   do {      dirty = false;       for( var i = 0; i < this.$$watchers.length; i++ ) {        var newValue = this.$$watchers[i].watchExp(),          oldValue = this.$$watchers[i].last;         if( oldValue !== newValue ) {          this.$$watchers[i].listener(newValue, oldValue);           dirty = true;           this.$$watchers[i].last = newValue;        }      }  } while(dirty);};  var $scope = new Scope(); $scope.name = 'Ryan'; $scope.$watch(function(){  return $scope.name;}, function( newValue, oldValue ) {  console.log(newValue, oldValue);} );   $scope.$digest();


Successful! We have now implemented the dirty Value Check (although this is the simplest form )! The above code will output the following content in the console:

Ryan undefined


This is the result we want-the value before $ scope. name is undefined, and the current value is Ryan.

Now we bind the $ digest function to the keyup event of an input element. This means that we do not need to call $ digest ourselves. This also means that we can now bind two-way data!

var Scope = function( ) {  this.$$watchers = []; }; Scope.prototype.$watch = function( watchExp, listener ) {  this.$$watchers.push( {    watchExp: watchExp,    listener: listener || function() {}  } );}; Scope.prototype.$digest = function( ) {  var dirty;   do {      dirty = false;       for( var i = 0; i < this.$$watchers.length; i++ ) {        var newValue = this.$$watchers[i].watchExp(),          oldValue = this.$$watchers[i].last;         if( oldValue !== newValue ) {          this.$$watchers[i].listener(newValue, oldValue);           dirty = true;           this.$$watchers[i].last = newValue;        }      }  } while(dirty);};  var $scope = new Scope(); $scope.name = 'Ryan'; var element = document.querySelectorAll('input'); element[0].onkeyup = function() {  $scope.name = element[0].value;   $scope.$digest();}; $scope.$watch(function(){  return $scope.name;}, function( newValue, oldValue ) {  console.log('Input value updated - it is now ' + newValue);   element[0].value = $scope.name;} ); var updateScopeValue = function updateScopeValue( ) {  $scope.name = 'Bob';  $scope.$digest();};


Using the above Code, whenever we change the input value, the name attribute in $ scope will change accordingly. This is the secret of two-way Data Binding hidden under the mysterious coat of AngularJS!

For more information about how to use two-way data binding in the AngularJS framework, see PHP!

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.