patternjavascriptangularCritical
What are the nuances of scope prototypal / prototypical inheritance in AngularJS?
Viewed 0 times
angularjsarenuancestheprototypicalinheritancewhatscopeprototypal
Problem
The API Reference Scope page says:
A scope can inherit from a parent scope.
The Developer Guide Scope page says:
A scope (prototypically) inherits properties from its parent scope.
A scope can inherit from a parent scope.
The Developer Guide Scope page says:
A scope (prototypically) inherits properties from its parent scope.
- So, does a child scope always prototypically inherit from its parent scope?
- Are there exceptions?
- When it does inherit, is it always normal JavaScript prototypal inheritance?
Solution
Quick answer:
A child scope normally prototypically inherits from its parent scope, but not always. One exception to this rule is a directive with
As for the nuances, scope inheritance is normally straightfoward... until you need 2-way data binding (i.e., 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 the way most people expect it should work. The child scope gets its own property that hides/shadows the parent property of the same name. Your workarounds are
New AngularJS developers often do not realize that
This issue with primitives can be easily avoided by following the "best practice" of always have a '.' in your ng-models – watch 3 minutes worth. Misko demonstrates the primitive binding issue with
Having a '.' in your models will ensure that prototypal inheritance is in play. So, use
L-o-n-g answer:
JavaScript Prototypal Inheritance
Also placed on the AngularJS wiki: https://github.com/angular/angular.js/wiki/Understanding-Scopes
It is important to first have a solid understanding of prototypal inheritance, especially if you are coming from a server-side background and you are more familiar with class-ical inheritance. So let's review that first.
Suppose parentScope has properties aString, aNumber, anArray, anObject, and aFunction. If childScope prototypically inherits from parentScope, we have:
(Note that to save space, I show the
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:
Suppose we then do this:
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.
Suppose we then do this:
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.)
Suppose we then do this:
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.
Takeaways:
One last scenario:
We deleted the childScope property first, then when we try to access the property again, the prototype chain is consulted.
Angular Scope Inheritance
The contenders:
Note, by default, directives do not create new scope -- i.e., the default is
ng-include
Suppose we have in our controller:
```
$scope.myPrimitive = 50;
$scope.myObject = {aNum
A child scope normally 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.As for the nuances, scope inheritance is normally straightfoward... until you need 2-way data binding (i.e., 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 the way most people expect it should work. The child scope gets its own property that hides/shadows the parent property of the same name. Your workarounds are
- define objects in the parent for your model, then reference a property of that object in the child: parentObj.someProp
- use $parent.parentScopeProperty (not always possible, but easier than 1. where possible)
- define a function on the parent scope, and call it from the child (not always possible)
New AngularJS developers often do not 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 have 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:
JavaScript Prototypal Inheritance
Also placed on the AngularJS wiki: https://github.com/angular/angular.js/wiki/Understanding-Scopes
It is important to first have a solid understanding of prototypal inheritance, especially if you are coming from a server-side background and you are more familiar with class-ical inheritance. So let's review that first.
Suppose parentScope has properties aString, aNumber, anArray, anObject, and aFunction. If childScope prototypically inherits from parentScope, we have:
(Note that to save space, I show the
anArray object as a single blue object with its three values, rather than an single blue object with three separate gray literals.)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 = 'child string'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.
Suppose we then do this:
childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'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.)
Suppose we then do this:
childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }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.
Takeaways:
- If we read childScope.propertyX, and childScope has propertyX, then the prototype chain is not consulted.
- If we set childScope.propertyX, the prototype chain is not consulted.
One last scenario:
delete childScope.anArray
childScope.anArray[1] === 22 // trueWe deleted the childScope property first, then when we try to access the property again, the prototype chain is consulted.
Angular Scope Inheritance
The contenders:
- The following create new scopes, and inherit prototypically: ng-repeat, ng-include, ng-switch, ng-controller, directive with
scope: true, directive withtransclude: true.
- The following creates a new scope which does not inherit prototypically: directive with
scope: { ... }. This creates an "isolate" scope instead.
Note, by default, directives do not create new scope -- i.e., the default is
scope: false.ng-include
Suppose we have in our controller:
```
$scope.myPrimitive = 50;
$scope.myObject = {aNum
Code Snippets
<input type="text" ng-model="someObj.prop1">
<!--rather than
<input type="text" ng-model="prop1">`
-->childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'childScope.aString = 'child string'childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }Context
Stack Overflow Q#14049480, score: 1754
Revisions (0)
No revisions yet.