Angularjs is an excellent front-end JS framework, has been used in a variety of Google products. Angularjs has many characteristics, the most core is: MVVM, modular, automated two-way data binding, semantic tagging, dependency injection and so on.
A. What is data two-way binding
Angular implements the bidirectional binding mechanism. The so-called two-way binding, nothing but the interface from the operation can be real-time reflected to the data, changes in the data can be real-time display to the interface.
One of the simplest examples is this:
<div ng-controller= "Counterctrl" >
<span ng-bind= "Counter" ></span>
<button ng-click= " counter++ ">increase</button>
</div>function Counterctrl ($scope) {
$scope. counter = 1;
}
This example is very simple, each time you click the button, the number on the interface increases one.
Two. Data two-way binding principle
1. Deep understanding
to achieve the user control of the mobile phone list display order characteristics. Dynamic sorting can be implemented by adding a new model attribute, integrating it with the iterator, and then getting data binding to do the rest.
Template (app/index.html)
Search: <input ng-model= "Query" >
Sort by:
<select ng-model= "Orderprop" >
<option value= " Name >Alphabetical</option>
<option value= "age" >Newest</option>
</select>
<ul class= "Phones" >
<li ng-repeat= "Phone in phones | filter:query | orderby:orderprop" >
{{ Phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
The following changes were made in index.html:
First, add a <select> tag called Orderprop, so that users can choose between the two sorting methods provided.
Then, after the filter filter, add an order by filter to handle the data entering the iterator. The order by filter takes an array as input, copies a copy, and then sorts and then prints the copy back to the iterator.
Angularjs creates a two-way binding between the Select element and the Orderprop model. The Orderprop will then be used as the input to the filter by.
When the data model changes (for example, the user selects a different order in the Drop-down menu), ANGULARJS data binding makes the view automatically updated. There is no clumsy DOM operation.
Controller (App/js/controllers.js)
function Phonelistctrl ($scope) {
$scope. phones = [
{' name ': ' Nexus S ',
' snippet ': ' Fast just got faster with N Exus S. ", Age
: 0},
{" name ":" Motorola xoom™with Wi-Fi ",
" snippet ":" The next, next Generation tablet. ",
' age ': 1},
{"name": "MOTOROLA xoom™",
"snippet": "The next, next Generation tablet.",
"Age": 2}
];< c12/> $scope. Orderprop = ' age ';
}
Modified the phones model--an array of handsets--added an age attribute for each cell phone. Sort the phone according to the age attribute.
Add a line to the controller code so that the default value for Orderprop is age. If we do not set the default value, the model is uninitialized until the user chooses an order in the Drop-down menu.
Now we should talk about two-way data binding. Note that when the application is loaded in a browser, "newest" is selected in the Drop-down menu. This is because we set the Orderprop to ' age ' in the controller. So bindings work in the direction from our model to the user interface-the binding of data from model to view. Now when you select "Alphabetically" in the Drop-down menu, the data model is updated at the same time, and the array of phone listings is reordered. This is when data binding works in another direction-the binding of data from view to model.
2. Principle Analysis
The following principle idea is actually very basic and can be considered as a 3 step plan:
- We need a way to bind UI elements and attributes to each other
- We need to monitor changes in attributes and UI elements
- We need to get all the bound objects and elements to feel the change.
There are still many ways to achieve the above ideas, there is a simple and effective way is to use the PubSub mode. The idea is simple: we use data attributes to bind HTML code, and all the JavaScript objects and DOM elements that are bound together subscribe to a PubSub object. Whenever a JavaScript object or an HTML INPUT element is monitoring the changes in the data, the events bound to the PubSub object are triggered, so that other bound objects and elements are changed accordingly.
3. Publisher-Subscriber mode (pubsub mode)
The main motivation behind designing this pattern is to facilitate the formation of loose coupling. In this pattern, not one object invokes a method of another object, but one object subscribes to a specific activity of another object and is notified after the state has changed. Subscribers are also called observers, and the objects that are observed are called publishers or topics. When an important event occurs, the Publisher notifies (invokes) all subscribers and may often pass messages as event objects.
Suppose there is a publisher, paper, who publishes newspapers and monthly magazines every day. Subscriber Joe will be notified of any news that has occurred.
The paper object requires a subscribers property, which is an array that stores all subscribers. The subscription behavior simply adds it to the array. When an event occurs, paper loops through the list of subscribers and notifies them. A notification means that a method of the Subscriber object is invoked. So when a user subscribes to a message, the subscriber needs to provide one of its methods to the paper subscribe ().
Paper also provides a unsubscribe () method that represents the deletion of subscribers from the Subscriber array (that is, the Subscribers property). The last important method of paper is publish (), which invokes the methods of these subscribers, and in general, the Publisher object paper needs to have these members:
- ①subscribers an array
- ②subscribe () To add subscribers to the subscribers array
- ③unsubscribe () Removing subscribers from the subscribers array
- ④publish () loops through each element of the subscribers array and invokes the method that they provide when they register
All three methods require a type parameter because the Publisher may trigger multiple events (such as publishing a magazine and a newspaper at the same time) and the user may choose to subscribe to one of them instead of the other.
Since these members are common to any Publisher object, it is meaningful to implement them as part of a separate object. That way we can copy it to any object and turn any given object into a publisher.
Three. Use jquery to do a simple implementation
for the subscription and publication of DOM events, it is very simple to use jquery, and then we'll use jquery like this:
function DataBinder (object_id) {//Use a JQuery object as simple PubSub var PubSub = JQuery ({});
We expect a ' data ' element specifying the binding//In the form:data-bind-<object_id>= "<property_name>"
var data_attr = "bind-" + object_id, message = object_id + ": Change"; Listen to change events on elements with the data-binding attributes and proxy//them to the PubSub E is ' broadcasted ' to ' all ' connected objects jQuery (document). On ("Change", "[data-" + data_attr + "]", Function (EVT)
{var $input = JQuery (this);
Pubsub.trigger (message, [$input. Data (data_attr), $input. Val ()]);
}); PubSub propagates changes to all bound elements, setting value of//input tags or HTML content of the other tags PubSub. On (Message, function (EVT, prop_name, New_val) {jQuery ("[data-" + data_attr + "=" + Prop_name + "]"). each (function
() {var $bound = JQuery (this);
if ($bound. Is ("Input, textarea, select")) { $bound. Val (new_val);
else {$bound. html (new_val);
}
});
});
return pubSub;
}
For the above implementation, here is the simplest implementation of a user model:
function User (UID) {
var binder = new DataBinder (UID),
user = {
attributes: {},
//The attribute Sette R Publish changes using the DataBinder PubSub
set:function (Attr_name, Val) {
this.attributes[attr_name] = val ;
Binder.trigger (UID + ": Change", [Attr_name, Val, this]);
},
get:function (attr_name) {return
this.attr ibutes[attr_name];
},
_binder:binder
};
Subscribe to the PubSub
Binder.on (uid + ": Change", Function (evt, attr_name, New_val, initiator) {
if (INI Tiator!== user) {
user.set (attr_name, new_val);
}
});
return user;
}
Now if we want to bind the user model properties to the UI, we just need to bind the appropriate data attributes to the corresponding HTML elements.
JavaScript
var user = new User (123);
User.set ("name", "Wolfgang");
HTML
<input type= "number" data-bind-123= "name"/>
Such input values are automatically mapped to the Name property of the user object, and vice versa. This simple implementation is complete.
Four. Angular implementation of data two-way binding
Angular mainly through the scopes implementation of data two-way binding. The Angularjs scopes consists of the following four main sections:
Digest cycles as well as dirty-checking, including Watch,digest, and $apply.
Scope inheritance-This mechanism allows us to create scope inheritance to share data and events.
Valid dirty-checking for Collections – Arrays and objects –.
Event Systems-On,emit, and $broadcast.
We mainly explain how the first angular data binding is implemented.
1.digest cycles and dirty-checking, including Watch,digest, and $apply
① Browser event loops and Angular.js extensions
Our browsers have been waiting for events, such as user interaction. If you click a button or enter something in the input box, the callback function of the event executes in the JavaScript interpreter, and then you can do any DOM operation, and the browser will change the DOM accordingly when the callback function is finished. Angular expands this event loop to generate an execution environment that sometimes becomes the angular context (this is an important concept).
②watch Queue (watch list)
Every time you bind something to your UI, you insert a $watch into the $watch team column. Imagine that $watch is something that can detect changes in the model it watches.
When our template is loaded, that is, in the linking phase (angular is divided into compile and linking phases---translator), the angular interpreter looks for each directive and then generates each $watch that is needed.
③ $digest Cycle
Remember the extended event loop I mentioned earlier? The digest loop triggers when the browser receives an event that can be processed by the angular context. This loop is composed of two smaller loops. One handles the Evalasync queue and the other handles the watch queue. What does this deal with? Digest will traverse our watch and ask if it has any changes in attributes and values, and the straight $watch queues have been checked.
This is called the dirty-checking. Since all the $watch have been checked, it is necessary to ask: Has $watch been updated? If there is at least one update, the loop will trigger again until all the $watch are unchanged. This will ensure that each model will no longer change. Remember that if the loop is more than 10 times, it throws an exception to prevent an infinite loop. When the $digest loop ends, the DOM changes accordingly.
For example: Controllers.js
App.controller (' Mainctrl ', function () {
$scope. Name = "Foo";
$scope. Changefoo = function () {
$scope. Name = "Bar";
}
);
{{Name}}
<button ng-click= "Changefoo ()" >change the name</button>
Here we have a $watch because the Ng-click does not generate $watch (the function does not change).
- We press the button.
- The browser receives an event and goes into the angular context (which explains why later).
- $digest loop begins execution, querying each $watch for changes.
- Because monitoring $scope.name $watch reports changes, it forces a $digest loop to be executed again.
- The new $digest loop does not detect changes.
- The browser takes back control and updates the DOM for the corresponding portion of the $scope.name new value.
It's important here (and a lot of people's very sore places) that every event entering the angular context performs a $digest loop, which means that every time we enter a letter loop, we check all the $watch of the entire page.
④ through $apply to enter the angular context
Who decides what events enter the angular context and which do not enter? $apply!
If you invoke apply when the event is triggered, it goes into the angularcontext and does not enter if it is not invoked. Now you may ask: I did not invoke apply in the example I just mentioned, why? Angular did it for you! So when you click on an element with Ng-click, the time is encapsulated into an apply call. If you have a ng−model= "foo" input box, and then you knock an F, the event will call apply ("Foo = ' F ';").
When will angular automatically apply for us? This is a common sore spot for angular novices. Why doesn't my jquery update what I'm bound to? Since jquery did not invoke apply, the event did not enter the angular context, $digest loop was never executed.
2. Concrete implementation
Angularjs's scopes is a generic JavaScript object on which you can bind your favorite attributes and other objects, but they are also added to the function to observe changes in the data structure. The functions of these observations are implemented by Dirty-checking and are performed in a digest loop.
①scope objects
Create a Test/scope_spec.js file and add the following test code to it:
Test/scope_spec.js
-------
/* Jshint globalstrict:true * * *
Global scope:false * *
"use strict";
Describe ("Scope", function () {
It ("can be constructed and used as Object", function () {
var scope = new Scope () ;
Scope.aproperty = 1;
Expect (Scope.aproperty). Tobe (1);
});
)
This test is used to create a scope and assign an arbitrary value to it. We can easily let this test pass: Create the Src/scope.js file and add the following:
Src/scope.js
------
/* jshint globalstrict:true *
/' use strict '; function Scope () {
}
In this test, we assign a property (Aproperty) to this scope. This is exactly how the properties on scope are run. They are normal JavaScript properties and there is nothing special about them. You don't have to call a particular setter here, and you don't need to limit what type of assignment you're assigning. The real magic lies in two special functions: Watch and digest. Let's take a look at these two functions now.
② Monitoring Object properties: Watch and Digest
Watch and digest are two sides of the same coin. They both form the core of the $digest cycle: Reacting to changes in the data.
To implement this feature, we first define a test file and assert that you can register a monitor using watch, and that the monitor's listener function is invoked when someone calls Digest.
Adds a nested describe block to the Scope_spec.js file. and create a Beforeeach function to initialize this scope so that we can repeat it for each test:
Test/scope_spec.js
------
Describe ("Scope", function () {
It ("can be constructed and used as Object", function () {var Scope = new Scope ();
Scope.aproperty = 1;
Expect (Scope.aproperty). Tobe (1);
});
Describe ("Digest", function () {
var scope;
Beforeeach (function () {scope = new scope ();
});
It ("calls the listener function of a watch on $digest", function () {var watchfn = function () {return ' Wat ';};
var listenerfn = Jasmine.createspy ();
Scope. $watch (WATCHFN, LISTENERFN);
Scope. $digest ();
Expect (LISTENERFN). tohavebeencalled ();
}); });
});
In the above test we called Watch to register a monitor on this scope. We now have little interest in the monitoring function itself, so we randomly provide a function to return a constant value. As a listening function, we provide a jasminespy. We then called the digest and checked to see if the listener was actually invoked.
First, this scope needs to have some place to store all the registered monitors. We will now add an array to store them in the scope constructor:
Src/scope.js
-----
Function Scope () {
this.$ $watchers = [];
}
The $$ prefix in the above code is considered a private variable in the ANGULARJS framework and should not be invoked outside of the application.
Now we can define the watch function. It receives two functions as parameters and stores them in the $watchers array. We want each of the scope objects to have this function, so we add it to the prototype of scope:
Src/scope.js
-----
scope.prototype. $watch = function (WATCHFN, LISTENERFN) {
var watcher = {
WATCHFN: WATCHFN,
listenerfn:listenerfn
};
this.$ $watchers. Unshift (watcher);
Finally we should have a digest function. Now, let's define a simplified version of the digest function that simply iterates through all the registration monitors and calls their listener functions:
Digest can continue to iterate over all the monitoring functions until the monitored values stop changing. Doing a few more digest is the change we can get from using the monitor and relying on other monitors.
First, we create a new name of $ $digestOnce and adjust it so that it can run on all monitors, and then return a Boolean value to indicate if there are any changes:
Src/scope.js
----
scope.prototype.$ $digestOnce = function () {
var length = this.$ $watchers. length;
var watcher, NewValue, OldValue, dirty;
while (length--) {
watcher = this.$ $watchers [length];
NewValue = WATCHER.WATCHFN (this);
Oldvalue= Watcher.last;
if (newvalue!== oldValue) {
Watcher.last = = newvalue;
WATCHER.LISTENERFN (NewValue, OldValue, this);
Dirty = true;
}
}
return dirty;
};
We then redefine digest so that it can run an "outer loop" and call $digestonce when the change occurs:
Src/scope.js
-----
scope.prototype. $digest = function () {
var dirty;
do {
dirty = this.$ $digestOnce ();
} while (dirty);
The above is angular data two-way binding of the relevant introduction, I hope to help you learn.