Data binding
By binding a text input box to the Person.name property, we can make our application a little more interesting. This step establishes the bidirectional binding of the text input box to the page.
In this context "bidirectional" means that if view changes the attribute value, the model "sees" the change, and if model changes the attribute value, view will also "see" the change. Angular.js for you to set up this mechanism automatically. If you're curious about how this is done, look at an article we launched later, which delves into the workings of digest_loop.
To create this binding, we use the Ng-model instruction attribute on the text entry box, like this:
<div ng-controller= "Mycontroller" >
<input type= "text" ng-model= "Person.name" placeholder= "Enter your Name '/>
Now that we've created a data binding (yes, that's easy), let's see how the View changes model:
Try:
When you type in the text box, the following name is automatically changed, which shows One direction of our data binding: from view to model.
We can also change the model in our (client) background to see that this change is automatically reflected in the front end. To demonstrate this process, let's write a timer function in Mycontroller's model to update a data on $scope. In the following code, we'll create this timer function, which is timed every second (like a clock) and updates the clock variable data on the $scope:
App.controller (' Mycontroller ', function ($scope) {
$scope. person = {Name: "Ari Lerner"};
var updateclock = function () {
$scope. Clock = new Date ();
The var timer = setinterval (function () {
$scope. $apply (Updateclock);
}, 1000);
Updateclock ();
});
As you can see, when we change the data for the clock variable in model, view automatically updates to reflect the change. With curly braces we can simply let the value of the clock variable appear in the view:
<div ng-controller= "Mycontroller" >
Interactive
Before we bind the data to the text input box. Note that data binding is not limited to data, and we can also use bindings to call functions in $scope (which has been mentioned before).
For a button, a link, or any other DOM element, we can implement the binding with another directive attribute: Ng-click. This ng-click instruction binds the mouse-click event of the DOM element (that is, the MouseDown browser event) to a method that is invoked when the browser triggers a click event on the DOM element. Similar to the previous example, this binding code is as follows:
<div ng-controller= "Democontroller" >
Both the button and the link are bound to all of the Controller $scope objects that contain their DOM elements, and when they are clicked by the mouse, angular invokes the corresponding method. Note that when we tell angular what method to call, we write the method name into the quoted string.
App.controller (' Democontroller ', function ($scope) {
$scope. counter = 0;
$scope. Add = function (amount) {$scope. Counter + + = amount;
$scope. Subtract = function (amount) {$scope. Counter-= amount;}
);
Please see:
$scope. $watch
$scope. $watch (watchexp, Listener, objectequality);
To monitor changes in a variable, you can use the $scope. $watch function. This function has three parameters that indicate what "to observe" (WATCHEXP), "What happens when you change" (listener), and whether you want to monitor a variable or an object. When we examine a parameter, we can ignore the third argument. For example, the following 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 the $scope. You can output $scope in the console to view the registered items in the $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!
For the first argument of $wach, you can also use a string. This is exactly the same as providing a function. As you can see in Angularjs's source code, 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 automatically returns the variables in the scope where we have already developed names.
$ $watchers
the $ $watchers variable in the $scope holds all the monitors that we define. If you look at the $ $watchers in the console, you will find that it is an array of objects.
$ $watchers = [
{
eq:false,//Indicates whether we need to check object level equality
fn:function (NewValue, OldValue) {},//This is the listener function we provided
last: ' Ryan ',///variable's latest value
exp:function () {},//We provide WATCHEXP function
get:function () {}//angular ' s compiled watchexp function c16/>}
];
The $watch function returns a Deregisterwatch function. This means that if we use the $scope $watch to monitor a variable, we can also stop monitoring later by calling a function.
$scope. $apply
when a controller/instruction/wait thing runs in Angularjs, a function called $scope $apply is run inside the ANGULARJS. The $apply function takes a function as a parameter and runs it, after which the $digest function is run on the rootscope.
The Angularjs $apply function code looks like this:
$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 argument in the code above is the argument you pass when calling $scope. $apply ()-but most of the time you may not be using the $apply function and remember to pass it a parameter when you use it.
Let's take a look at how Ng-keydown is using $scope. $apply. In order to register this directive, ANGULARJS will use the following code.
var ngeventdirectives = {};
ForEach (
' click DblClick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress s Ubmit focus Blur copy cut Paste '. Split ('),
function (name) {
var directivename = directivenormalize (' ng-' + name) ;
Ngeventdirectives[directivename] = [' $parse ', function ($parse) {return
{
compile:function ($element, attr) { C8/>VAR fn = $parse (Attr[directivename]);
return function Ngeventhandler (scope, Element) {
Element.on (lowercase (name), function (event) {
scope.$ Apply (function () {
fn (scope, {$event: event});
});
}
;
};};
What the above code does is loop through different types of events, which can then be triggered and create a new instruction called ng-[an event. In the compile function of the instruction, it registers an event handler on the element, which corresponds to the name of the instruction one by one. When the event is set off, Angularjs runs the scope. $apply function and let it run a function.
Is it just one-way data binding?
The ng-keydown mentioned above can only change the value in the $scope associated with the element value – this is just a single data binding. This is why this instruction is called Ng-keydown, and only when the KeyDown event is triggered can you give us a new value.
But what we want is two-way data binding!
Let's take a look at Ng-model now. When you are 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 event-handling instructions (such as KeyDown) to the input elements we use-this is where the $scope $apply been invoked! and the $scope. $watch is invoked in the controller of the instruction. You can see this in the following code:
$scope. $watch (function Ngmodelwatch () {
var value = Ngmodelget ($scope);
If the scope model and Ngmodel values do not synchronize 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 a parameter on the call to the $scope $watch, this function will be invoked regardless of what has changed in the scope. In Ng-model, this function is used to check that the model and view have no synchronization, and that if there is no synchronization, it will update the model data with the new value. This function returns a new value, and when it runs in the $digest function, we know what this value is!
Why is our listener not triggered?
If we stop this listener in the $scope $watch listener function, the listener will not be triggered even if we update $scope.name.
As mentioned earlier, ANGULARJS will run $scope in the controller function of each instruction. $apply. If we look at the code for the $scope. $apply function, we find that it only runs the $digest function after the controller function has been invoked-which means that if we stop listening immediately, $scope. $watch function is not even called! But how does it work?
$digest function will be called in the $rootscope by the $scope. $apply. It will run the digest loop in the $rootscope, then go down through each scope and run the loop on each scope. In a simple scenario, the digest loop triggers all the WATCHEXP functions in the $ $watchers variable, comparing them to the most recent value, triggering the listener if the value is not the same.
When the digest loop runs, it iterates through all the listeners and loops again, as long as the loop finds the "dirty value" and the loop continues. If the value of the watchexp is not the same as the latest value, the loop is considered to have found a dirty value. Ideally it will run once, and if it runs over 10 times, you'll see an error.
So when $scope. $apply Run, the $digest also runs, and it loops through the $ $watchers, and 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 one 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 manually run $scope. $apply (): This allows Angularjs to realize that its scope has changed.
Create your own dirty value check
so far, we've been able to create a compact, simplified version of the dirty value check. Of course, the dirty value check in the ANGULARJS is more advanced than the comparison, which provides crazy asynchronous queues and other advanced features.
Set scope
scope is just a function, which contains any objects we want to store. We can extend the prototype object of this function to replicate $digest and $watch. We do not need $apply methods, because we do not need to perform any functions in the context of the scope-we simply need to use $digest. The code for our scope looks like this:
var Scope = function () {
this.$ $watchers = [];
};
Scope.prototype. $watch = function () {
};
Scope.prototype. $digest = function () {
};
Our $watch function needs to accept two parameters, Watchexp and listener. When $watch are called, we need to push them into the scope $ $watcher array.
var Scope = function () {
this.$ $watchers = [];
};
Scope.prototype. $watch = function (watchexp, listener) {
this.$ $watchers. Push ({
watchexp:watchexp,
Listener:listener | | function () {}
});
Scope.prototype. $digest = function () {
};
As you may have noticed, if listener is not provided, we will set listener to an empty function – so that we can $watch all the variables.
Next we will create the $digest. We need to check whether the old values are equal to the new values, and if they are not equal, the listener is triggered. We'll loop through the process until the two are equal. This is the source of the "dirty Value" – the dirty value means that the new value is not equal to the old value!
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 of the scope. We assign this instance to $scope. We will then register a listener function to run the $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 ();
It worked! We have now implemented the dirty value check (although this is the simplest form)! The above code will output the following in the console:
This is exactly what we want-$scope. Name before the value is undefined, and now the value is Ryan.
Now we bind the $digest function to the KeyUp event of an INPUT element. That means we don't have to call $digest ourselves. This also means that we can now implement bidirectional data binding!
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 (), old
Value = 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-i T 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 value of input, the Name property in $scope will change accordingly. This is hidden in the Angularjs mystery cloak the data two-way binding secret!