HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptangularCritical

What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

Submitted by: @import:stackoverflow-api··
0
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.

  • 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 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  // true


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:

  • The following create new scopes, and inherit prototypically: ng-repeat, ng-include, ng-switch, ng-controller, directive with scope: true, directive with transclude: 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.