Home >Web Front-end >JS Tutorial >How Does Prototypal Inheritance Work with Two-Way Data Binding in AngularJS Scopes?
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
Quick Answer:
A child scope typically prototypically inherits from its parent scope, but not always. One exception to this rule is a directive with scope: { ... } -- this creates an "isolate" scope that does not prototypically inherit. This construct is often used when creating a "reusable component" directive.
Regarding the nuances, scope inheritance is generally straightforward... until you need 2-way data binding (e.g., form elements, ng-model) in the child scope. Ng-repeat, ng-switch, and ng-include can trip you up if you try to bind to a primitive (e.g., number, string, boolean) in the parent scope from inside the child scope. It doesn't work as most people expect it would. The child scope gets its own property that hides/shadows the parent property of the same name. Your workarounds are:
New AngularJS developers often don't realize that ng-repeat, ng-switch, ng-view, ng-include, and ng-if all create new child scopes, so the problem often shows up when these directives are involved. (See this example for a quick illustration of the problem.)
This issue with primitives can be easily avoided by following the "best practice" of always having a '.' in your ng-models – watch 3 minutes worth. Misko demonstrates the primitive binding issue with ng-switch.
Having a '.' in your models will ensure that prototypal inheritance is in play. So, use:
L-o-n-g Answer:
Understanding Prototypal Inheritance
It's important to understand prototypal inheritance before discussing scope inheritance.
Suppose parentScope has properties aString, aNumber, anArray, anObject, and aFunction. If childScope prototypically inherits from parentScope, we have:
[Image of prototypal inheritance diagram]
If we try to access a property defined on the parentScope from the child scope, JavaScript will first look in the child scope, not find the property, then look in the inherited scope, and find the property. (If it didn't find the property in the parentScope, it would continue up the prototype chain... all the way up to the root scope). So, these are all true:
childScope.aString === 'parent string' childScope.anArray[1] === 20 childScope.anObject.property1 === 'parent prop1' childScope.aFunction() === 'parent output'
Suppose we then do this:
childScope.aString === 'parent string' childScope.anArray[1] === 20 childScope.anObject.property1 === 'parent prop1' childScope.aFunction() === 'parent output'
The prototype chain is not consulted, and a new aString property is added to the childScope. This new property hides/shadows the parentScope property with the same name. This will become very important when we discuss ng-repeat and ng-include below.
[Image of property hiding diagram]
Suppose we then do this:
childScope.aString = 'child string'
The prototype chain is consulted because the objects (anArray and anObject) are not found in the childScope. The objects are found in the parentScope, and the property values are updated on the original objects. No new properties are added to the childScope; no new objects are created. (Note that in JavaScript arrays and functions are also objects.)
[Image of follow the prototype chain diagram]
Suppose we then do this:
childScope.anArray[1] = '22' childScope.anObject.property1 = 'child prop1'
The prototype chain is not consulted, and child scope gets two new object properties that hide/shadow the parentScope object properties with the same names.
[Image of more property hiding diagram]
Angular Scope Inheritance
The contenders:
By default, directives do not create a new scope (i.e., scope: false).
ng-include
Suppose we have in our controller:
childScope.anArray = [100, 555] childScope.anObject = { name: 'Mark', country: 'USA' }
And in our HTML:
$scope.myPrimitive = 50; $scope.myObject = {aNumber: 11};
Each ng-include generates a new child scope, which prototypically inherits from the parent scope.
[Image of ng-include child scopes diagram]
Typing (say, "77") into the first input textbox causes the child scope to get a new myPrimitive scope property that hides/shadows the parent scope property of the same name.
[Image of ng-include with a primitive diagram]
Typing (say, "99") into the second input textbox does not result in a new child property. Because tpl2.html binds the model to an object property, prototypal inheritance kicks in when the ngModel looks for object myObject -- it finds it in the parent scope.
[Image of ng-include with an object diagram]
We can rewrite the first template to use $parent, if we don't want to change our model from a primitive to an object:
<script type="text/ng-template">
Typing (say, "22") into this input textbox does not result in a new child property. The model is now bound to a property of the parent scope (because $parent is a child scope property that references the parent scope).
[Image of ng-include with $parent diagram]
ng-switch
Ng-switch scope inheritance works just like ng-include. So if you need 2-way data binding to a primitive in the parent scope, use $parent, or change the model to be an object and then bind to a property of that object. This will avoid child scope hiding/shadowing of parent scope properties.
ng-repeat
Ng-repeat works a little differently. Suppose we have in our controller:
childScope.aString === 'parent string' childScope.anArray[1] === 20 childScope.anObject.property1 === 'parent prop1' childScope.aFunction() === 'parent output'
And in our HTML:
childScope.aString = 'child string'
For each item/iteration, ng-repeat creates a new scope, which prototypically inherits from the parent scope, but it also assigns the item's value to a new property on the new child scope (the new property is the loop variable name). Here's what the Angular source code for ng-repeat actually is:
childScope.anArray[1] = '22' childScope.anObject.property1 = 'child prop1'
If the item is a primitive (as in myArrayOfPrimitives), essentially a copy of the value is assigned to the new child scope property. Changing the child scope property's value (i.e., using ng-model, hence child scope num) does not change the array the parent scope references. So in the first ng-repeat above, each child scope gets a num property that is independent of the myArrayOfPrimitives array:
[Image of ng-repeat with primitives diagram]
This ng-repeat will not work (like you want/expect it to). Typing into the textboxes changes the values in the gray boxes, which are only visible in the child scopes. What we want is for the inputs to affect the myArrayOfPrimitives array, not a child scope primitive property. To accomplish this, we need to change the model to be an array of objects.
So, if the item is an object, a reference to the original object (not a copy) is assigned to the new child scope property. Changing the child scope property's value (i.e., using ng-model, hence obj.num) does change the object the parent scope references. So in the second ng-repeat above, we have:
[Image of ng-repeat with objects diagram]
(I colored one line gray just so that it is clear where it is going.)
This works as expected. Typing into
The above is the detailed content of How Does Prototypal Inheritance Work with Two-Way Data Binding in AngularJS Scopes?. For more information, please follow other related articles on the PHP Chinese website!