In-depth exploration of the Scope object super tutorial in AngularJS framework, angularjss.pdf
I. Problems Encountered
The problem occurs when AngularJS is used to nest the Controller. Because each Controller has its corresponding Scope (equivalent to Scope and control Scope), the nested Controller means the nesting of Scope. In this case, what will happen if both scopes have a Model with the same name? How to update the Model in the parent Scope from the sub-Scope?
This problem is typical. For example, if the current page is a product list, you need to define a ProductListController
function ProductListController ($ scope, $ http) {
$ http.get ('/ api / products.json')
.success (function (data) {
$ scope.productList = data;
});
$ scope.selectedProduct = {};
}
You probably see that a Model of selectedProduct is also defined in Scope, which means that a certain product is selected. At this time, the product details will be obtained, and the page is automatically updated by $ routeProvider in AngularJS, pull the new details page template, there is a ProductDetailController in the template
function ProductDetailController ($ scope, $ http, $ routeParams) {
$ http.get ('/ api / products /' + $ routeParams.productId + '. json')
.success (function (data) {
$ scope.selectedProduct = data;
});
}
An interesting thing happened, there is also a selectedProduct here, how does it affect the selectedProduct in ProductListController?
The answer is no effect. In AnuglarJS, the child Scope does inherit the objects in the parent Scope, but when you try the two-way data binding of the basic data types (string, number, boolean), you will find some strange behavior. Works like that. The attributes of the child Scope hide (override) the attributes of the same name in the parent Scope. Changes to the child Scope attributes (form elements) do not update the value of the parent Scope attribute. This behavior is actually not unique to AngularJS, this is how JavaScript's own prototype chain works. Developers usually do n’t realize that ng-repeat, ng-switch, ng-view and ng-include all create their new sub-scopes, so there are often problems when using these directives.
Second, the solution
The solution is to not use basic data types, but always add one more point to the Model.
use
<input type = "text" ng-model = "someObj.prop1">
To replace
<input type = "text" ng-model = "prop1">
Isn't it ridiculous? The following example clearly expresses the strange phenomenon that I want to express
app.controller ('ParentController', function ($ scope) {
$ scope.parentPrimitive = "some primitive"
$ scope.parentObj = {};
$ scope.parentObj.parentProperty = "some value";
});
app.controller ('ChildController', function ($ scope) {
$ scope.parentPrimitive = "this will NOT modify the parent"
$ scope.parentObj.parentProperty = "this WILL modify the parent";
});
View Online Demo DEMO
But what if I really need to use primitive data types such as string number? 2 methods-
Use $ parent.parentPrimitive in the child scope. This will prevent the child Scope from creating its own properties.
Define a function in the parent Scope, let the child Scope call, pass the parameters of the original data type to the father, thereby updating the attributes in the parent Scope. (Not always feasible)
Three, JavaScript's prototype chain inheritance
After vomiting, let's take a closer look at the prototype chain of JavaScript. This is very important, especially when you move from server-side development to the front-end, you should be familiar with the classic Class inheritance, let's review it.
Assume that the parent class parentScope has the following member attributes aString, aNumber, anArray, anObject, and aFunction. The childScope childScope prototype inherits the parent class parentScope, so we have:
If the child Scope tries to access the property defined in parentScope, JavaScript will first look in the child Scope, if there is no such property, it will find its inherited scope to get the property, if none of the inherited prototype object parentScope has the property, then continue Look in its prototype, from the prototype chain all the way up until it reaches rootScope. Therefore, the result of the following expression is ture:
childScope.aString === 'parent string'
childScope.anArray [1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction () === 'parent output'
Suppose we execute the following statement
childScope.aString = 'child string'
The prototype chain was not queried, but a new attribute aString was added to childScope. This new attribute hides (overrides) the attribute of the same name in parentScope. This concept is important when we discuss ng-repeat and ng-include below.
Suppose we perform this operation:
childScope.anArray [1] = '22'
childScope.anObject.property1 = 'child prop1'
The prototype chain was queried because the objects anArray and anObject were not found in childScope. They are found in parentScope and the value is updated. No new properties have been added to childScope, and no new objects have been created. (Note: In JavaScript, both array and function are objects)
Suppose we perform this operation:
childScope.anArray = [100, 555]
childScope.anObject = {name: 'Mark', country: 'USA'}
The prototype chain is not queried, and two new object properties have been added to the child scope, which hide (override) the object properties of the same name in the parentScope.
Should be able to summarize
If childScope.propertyX is read, and childScope has propertyX, then the prototype chain is not queried.
If childScope.propertyX is set, the prototype chain will not be queried.
The last case,
delete childScope.anArray
childScope.anArray [1] === 22 // true
We deleted the attribute from childScope, when we access the attribute again, the prototype chain will be queried. Deleting an object's attributes will bring up attributes from the prototype chain.
Fourth, AngularJS Scope inheritance
Create a new Scope, and prototype inheritance: ng-repeat, ng-include, ng-switch, ng-view, ng-controller, directive with scope: true, directive with transclude: true
Create a new Scope, but do not inherit: directive with scope: {...}. It will create an independent Scope.
Note: By default, the directive does not create a new scope, that is, the default parameter is scope: false.
ng-include
Suppose in our controller,
$ scope.myPrimitive = 50;
$ scope.myObject = {aNumber: 11};
The HTML is:
<script type = "text / ng-template" id = "/ tpl1.html">
<input ng-model = "myPrimitive">
</ script>
<div ng-include src = "'/ tpl1.html'"> </ div>
<script type = "text / ng-template" id = "/ tpl2.html">
<input ng-model = "myObject.aNumber">
</ script>
<div ng-include src = "'/ tpl2.html'"> </ div>
Each ng-include will generate a child Scope, and each child Scope inherits the parent Scope.
Enter (such as "77") into the first input text box, the child Scope will get a new myPrimitive attribute, overriding the parent Scope of the same name attribute. This may be different from what you expected.
Input (such as "99") to the second input text box, and will not create a new property in the sub Scope, because tpl2.html binds the model to an object property (an object property), prototype inheritance is played at this time After that, ngModel looks for the object myObject and finds it in its parent Scope.
If we don't want to change the model from the number base type to an object, we can use $ parent to rewrite the first template:
<input ng-model = "$ parent.myPrimitive">
Typing (such as "22") into this text box will not create new attributes. The model is bound to the property of the parent scope (because $ parent is a property of the child Scope pointing to its parent Scope).
For all scopes (prototype inherited or non-inherited), Angular will always record the parent-child relationship (that is, the inheritance relationship) through the Scope's $ parent, $$ childHead and $$ childTail properties. Draw these attributes.
In the absence of form elements, another method is to define a function in the parent Scope to modify the basic data type. Because of prototypal inheritance, the child scope ensures that this function can be called. E.g,
// In the parent Scope
$ scope.setMyPrimitive = function (value) {
$ scope.myPrimitive = value;
View DEMO
ng-switch
The prototype inheritance of ng-switch is the same as ng-include. So if you need to perform two-way binding on the basic type data, use $ parent, or change it to an object object and bind to the object's properties to prevent the child Scope from overwriting the properties of the parent Scope.
ng-repeat
ng-repeat is a little different. Suppose in our controller:
$ scope.myArrayOfPrimitives = [11, 22];
$ scope.myArrayOfObjects = [{num: 101}, {num: 202}]
And HTML:
<ul>
<li ng-repeat = "num in myArrayOfPrimitives">
<input ng-model = "num">
</ li>
<ul>
<ul>
<li ng-repeat = "obj in myArrayOfObjects">
<input ng-model = "obj.num">
</ li>
<ul>
For each Item, ng-repeat creates a new Scope, each Scope inherits the parent Scope, but at the same time the value of the item is also assigned to the new Scope New attribute (the name of the new attribute is the variable name of the loop). The source code of Angular ng-repeat is actually like this:
childScope = scope. $ new (); // The child scope prototype inherits the parent scope ...
childScope [valueIdent] = value; // Create a new childScope attribute
If item is a basic data type (like myArrayOfPrimitives), essentially its value is copied and assigned to the new sub-scope attribute. Changing the value of this child scope attribute (such as ng-model, or num) does not change the array referenced by the parent scope. So the num attribute obtained by each sub-scope in the first ng-repeat above is independent of the myArrayOfPrimitives array:
This ng-repeat is not what you expected. In Angular 1.0.2 and earlier versions, entering the value in the text box will change the gray grid, they are only visible in the sub-scope. After Angular 1.0.3+, the input text will no longer have any effect.
What we want is for the input to change the myArrayOfPrimitives array, not the properties in the sub-Scope. To do this, we must change the model to an array of objects.
So if item is an object, a reference (not a copy) to the original object is assigned to the new child Scope property. Changing the value of the child Scope property (using ng-model, ie obj.num) also changes the object referenced by the parent Scope. So the second ng-repeat above can be expressed as:
This is what we want. Entering into the text box will change the value of the gray grid, which is visible in both the parent Scope and the child Scope.
ng-controller
Using ng-controller for nesting, the result is normal prototypal inheritance like ng-include and ng-switch. So the practice is no longer repeated. However "two controllers using $ scope inheritance to share information is considered bad practice"
You should use service to share data between controllers.
If you really want to share data through inheritance, then there is nothing special to do, the child Scope can directly access all the properties of the parent Scope.
directives
This needs to be discussed in different situations.
The default scope: false – directive will not create a new scope, so there is no prototype inheritance. This looks simple, but it is also dangerous, because you think that the directive created a new attribute in Scope, but in fact it only uses an existing attribute. This is not good for writing reusable modules and components.
scope: true – At this time, the directive will create a new child scope and inherit the parent scope. If there are multiple directives on the same DOM node that all need to create a new scope, only one new scope will be created. Because there is normal prototype inheritance, so as with ng-include and ng-switch, we must pay attention to the two-way binding of the basic type data. The child Scope property will override the parent Scope property of the same name.
scope: {...} – At this time, the directive creates an independent scope without prototypal inheritance. This is a good choice when writing reusable modules and components, because the directive will not accidentally read and write the parent scope. However, sometimes such directives often need to access the attributes of the parent scope. The object hash is used to establish a two-way binding (using ‘=’) or a one-way binding (using ‘@ ') between this independent Scope and the parent Scope. There is also an ‘&’ expression to bind the parent Scope. These are all derived from the parent Scope to create local Scope properties. Note that HTML attributes are used to establish bindings. You cannot refer to the attribute name of the parent Scope in the object hash. You must use an HTML attribute. For example, <div my-directive> and scope: {localProp: '@parentProp'} cannot bind the parent property parentProp to a separate scope, you must specify it like this: <div my-directive the-Parent-Prop = parentProp> and scope: {localProp: '@theParentProp'}. In the independent scope, __proto__ refers to a Scope object (orange Object in the middle), and the $ parent of the independent scope points to the parent scope, so although it is independent and does not inherit from the parent Scope prototype, it is still a child scope.
In the picture below, we have <my-directive interpolated = "{{parentProp1}}" twowayBinding = "parentProp2"> and scope:
{interpolatedProp: '@interpolated', twowayBindingProp: '= twowayBinding'}.
At the same time, suppose that directive does scope.someIsolateProp = "I'm isolated" in its link function
Note: Use attrs. $ Observe ('attr_name', function (value) {...} in the link function to get the attribute value of the independent Scope replaced with the '@' symbol. For example, in the link function there is attrs. $ Observe ('interpolated', function (value) {...} The value will be set to 11. (scope.interpolatedProp is undefined in the link function, on the contrary scope.twowayBindingProp is defined in the link function because the '=' symbol is used )
transclude: true – At this time, the directive creates a new “transcluded” child scope and inherits the parent scope. So if the content in the template fragment (such as those that will replace ng-transclude) requires two-way binding to the basic type data of the parent Scope, use $ parent, or model the property of an object to prevent the child Scope property from overwriting the parent Scope Attributes.
Transcluded and independent scopes (if any) are siblings, and the $ parent of each scope points to the same parent scope. When the scope in the template and the independent scope exist at the same time, the independent scope property $$ nextSibling will point to the scope in the template.
In it, suppose that the directive is the same as the previous figure, but there is more transclude: true
Looking at the online DEMO, there is a showScope () function in the example that can be used to check the independent scope and its associated transcluded scope.
to sum up
There are four types of Scope:
Common Scope for prototype inheritance-ng-include, ng-switch, ng-controller, directive with scope: true
Ordinary prototype inherits Scope but copy assignment-ng-repeat. Each cycle of ng-repeat creates a new sub-scope, and the sub-scope always gets new attributes.
Independent isolate scope-directive with scope: {...}. It is not prototype inheritance, but ‘=’, ‘@’ and ‘&’ provide access to the parent Scope property.
transcluded scope —— directive with transclude: true. It also follows prototype inheritance, but it is also a sibling of any isolate scope.
For all scopes, Angular will always record the parent-child relationship through the scope's $ parent, $$ childHead and $$ childTail properties.
PS: The difference between scope and rootscope
Scope is the bridge between HTML and a single controller, and data binding depends on him. Rootscope is the bridge of scope in each controller. The values defined with rootscope can be used in various controllers. The following examples explain in detail.
1, js code
phonecatApp.controller ('TestCtrl', ['$ scope', '$ rootScope',
function ($ scope, $ rootScope) {
$ rootScope.name = 'this is test';
}
]);
phonecatApp.controller ('Test111Ctrl', ['$ scope', '$ rootScope',
function ($ scope, $ rootScope) {
$ scope.name = $ rootScope.name;
}
]);
2, html code
<div ng-controller = "TestCtrl">
I set the global variable. <Strong> {{$ root.name}} </ strong>
</ div>
<div ng-controller = "Test111Ctrl">
1, get global variable. <Strong> {{name}} </ strong> <br>
2, get global variable. <Strong> {{$ root.name}} </ strong>
</ div>
3. Display results
I set the global variable.this is test
1, get global variable .this is test
2, get global variable .this is test
It can be seen from the results that the variables set by $ rootScope.name can be displayed directly with {{$ root.name}} in all controllers, which is very powerful. Of course, it can also be assigned to scope.
Articles you may be interested in:
Guide to using the $ scope method in angularJS
Introduction to scope in javascript