Coder Perfect

In AngularJS, what are the intricacies of scope prototypal / prototypical inheritance?

Problem

According to the API Reference Scope page:

The Developer Guide Scope page says:

Asked by Mark Rajcok

Solution #1

A child scope often inherits from its parent scope, however this is not always the case. A directive with scope:… is an exception to this rule because it produces a “isolate” scope that does not inherit. When establishing a “reusable component” directive, this construct is frequently utilized.

In terms of intricacies, scope inheritance is usually straightforward… unless you need 2-way data binding in the child scope (i.e., form elements, ng-model). If you try to bind to a primitive (e.g., number, string, boolean) in the parent scope from within the child scope, ng-repeat, ng-switch, and ng-include can trip you up. It doesn’t work in the way that most people expect it to. The child scope has its own property that hides/shadows the same-named parent property. Your workarounds are effective.

Because many novice AngularJS developers are unaware that directives like ng-repeat, ng-switch, ng-view, ng-include, and ng-if all produce new child scopes, the issue frequently arises when these directives are used. (For a brief demonstration of the problem, see this example.)

This primitives issue can be easily prevented by following the “best practice” of always including a ‘.’ in your ng-models — watch the video for 3 minutes. Misko uses ng-switch to highlight the primitive binding issue.

The presence of a ‘.’ in your models ensures that prototypal inheritance is active. As a result, utilize

<input type="text" ng-model="someObj.prop1">

<!--rather than
<input type="text" ng-model="prop1">`
-->

Also also https://github.com/angular/angular.js/wiki/Understanding-Scopes on the AngularJS wiki.

It’s crucial to grasp prototypal inheritance first, especially if you’re coming from a server-side background where you’re more used to class-ical inheritance. So let’s go through that again.

Consider the properties aString, aNumber, anArray, anObject, and aFunction in parentScope. If childScope inherits from parentScope in a paradigmatic way, we get:

(Note that the anArray object is shown as a single blue object with three values rather than a single blue object with three separate gray literals to conserve space.)

If we try to access a property declared on the parentScope from the child scope, JavaScript will look in the child scope first, but not find the property, and then look in the inherited scope, where it will locate the property. (If the property was not found in the parentScope, it would go up the prototype chain, all the way to the root scope.) So, yes, all of this is correct:

childScope.aString === 'parent string'
childScope.anArray[1] === 20
childScope.anObject.property1 === 'parent prop1'
childScope.aFunction() === 'parent output'

So, let’s see what we can come up with:

childScope.aString = 'child string'

The prototype chain is ignored, and the childScope gains a new aString attribute. The parentScope property with the same name is hidden/shadowed by this new property. This will be crucial when we examine ng-repeat and ng-include farther down.

So, let’s see what we can come up with:

childScope.anArray[1] = '22'
childScope.anObject.property1 = 'child prop1'

Because the objects (anArray and anObject) aren’t found in the childScope, the prototype chain is used. The objects are located in the parentScope, and the original objects’ property values are updated. There are no new attributes or objects introduced to the childScope. (Note that arrays and functions are also objects in JavaScript.)

So, let’s see what we can come up with:

childScope.anArray = [100, 555]
childScope.anObject = { name: 'Mark', country: 'USA' }

The prototype chain is ignored, and the child scope receives two new object properties that hide/shadow the parentScope object properties of the same name.

Takeaways:

One last scenario:

delete childScope.anArray
childScope.anArray[1] === 22  // true

We initially erased the childScope property, and then the prototype chain is examined when we try to access it again.

The contenders:

Note that directives do not establish new scope by default — scope: false is the default.

Assume we have the following in our controller:

$scope.myPrimitive = 50;
$scope.myObject    = {aNumber: 11};

In addition, in our HTML:

<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 creates a new child scope, which inherits from the parent scope in most cases.

When you type (for example, “77”) into the first input textbox, a new myPrimitive scope property is created in the child scope, which hides/shadows the parent scope property of the same name. This is most likely not what you wanted or expected.

When you type (for example, “99”) into the second input textbox, no new child property is created. Prototypal inheritance kicks in when the ngModel seeks for object myObject, which it finds in the parent scope because tpl2.html ties the model to an object property.

If we don’t want to modify our model from a primitive to an object, we can alter the first template to use $parent:

<input ng-model="$parent.myPrimitive">

When you type (say, “22”) into this input textbox, no new child property is created. Because $parent is a child scope property that references the parent scope, the model is now tied to a property of the parent scope.

Angular always tracks a parent-child relationship (i.e., a hierarchy) for all scopes (prototypal or not) via scope properties $parent, $$childHead, and $$childTail. In most diagrams, I don’t indicate these scope properties.

Another option for instances when form components aren’t involved is to change the primitive with a function defined on the parent scope. Then, because prototypal inheritance makes this function available to the child scope, make sure the child always calls it. E.g.,

// in the parent scope
$scope.setMyPrimitive = function(value) {
     $scope.myPrimitive = value;
}

Here’s an example fiddle that employs the “parent function” strategy. (This answer includes a fiddle: https://stackoverflow.com/a/14104318/215945.)

Visit https://stackoverflow.com/a/13782671/215945 and https://github.com/angular/angular.js/issues/1267 for more information.

ng-switch inherits scope inheritance in the same way that ng-include does. Use $parent or transform the model to an object and bind to a property of that object if you need 2-way data binding to a primitive in the parent scope. This prevents child scope properties from being hidden or shadowed by parent scope properties.

See also AngularJS, bind scope of a switch-case?

Ng-repeat operates in a unique way. Assume we have the following in our controller:

$scope.myArrayOfPrimitives = [ 11, 22 ];
$scope.myArrayOfObjects    = [{num: 101}, {num: 202}]

In addition, in our 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>

ng-repeat produces a new scope for each item/iteration, which inherits from the parent scope in most cases, but also sets the item’s value to a new property on the new child scope. (The loop variable’s name is the name of the new property.) The Angular source code for ng-repeat looks like this:

childScope = scope.$new();  // child scope prototypically inherits from parent scope
...
childScope[valueIdent] = value;  // creates a new childScope property

If item is a primitive (as in myArrayOfPrimitives), the new child scope property receives a copy of the value. Changing the value of the child scope attribute (i.e., using ng-model, hence child scope num) has no effect on the array that the parent scope refers to. Each child scope gets a num property that is independent of the myArrayOfPrimitives array in the first ng-repeat above:

This ng-repeat isn’t going to work the way you want it to (or want it to). The values in the gray boxes, which are only visible in the child scopes, are changed by typing into the textboxes. Instead of affecting a child scope primitive property, we want the inputs to effect the myArrayOfPrimitives array. To do so, we’ll need to convert the model to an array of objects.

If item is an object, the new child scope property is set to a reference to the original object (not a clone). Changing the value of the child scope property (i.e., using ng-model, therefore obj.num) changes the object that the parent scope refers to. As a result, in the second ng-repeat, we have:

(I tinted one line gray to make it easier to see where it’s going.)

This performs just as planned. The values in the gray boxes, which are accessible to both the child and parent scopes, are changed by typing into the textboxes.

See also https://stackoverflow.com/a/13782671/215945 and Difficulty with ng-model, ng-repeat, and inputs.

As with ng-include and ng-switch, nesting controllers with ng-controller results in standard prototypal inheritance, thus the same principles apply. However, “sharing information via $scope inheritance across two controllers is considered bad form” — http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ Instead, a service should be used to share data among controllers.

(There is nothing you need to do if you actually want to exchange data via controller scope inheritance.) All of the parent scope’s properties will be accessible to the child scope. (See also Controller load order varies depending on whether you’re loading or navigating.)

The showScope() function in this fiddle can be used to inspect an isolate and transcluded scope. See the directions in the fiddle’s remarks.

Scopes are divided into four categories:

Angular always tracks a parent-child connection (i.e., a hierarchy) for all scopes (prototypal or not), using the attributes $parent and $$childHead and $$childTail.

Graphviz “*.dot” files, which are available on github, were used to create the diagrams. The use of GraphViz for the visualizations was inspired by Tim Caswell’s “Learning JavaScript with Object Graphs.”

Answered by Mark Rajcok

Solution #2

I don’t want to compete with Mark’s response, but I did want to highlight the part that, as a newcomer to Javascript inheritance and the prototype chain, ultimately made things click.

Only property reads, not writes, search the prototype chain. As a result, when you set

myObject.prop = '123';

It doesn’t check the chain, but when you set it up, it does.

myObject.myThing.prop = '123';

Within the write operation, there’s a subtle read that tries to search up myThing before writing to its prop. As a result, writing to object is necessary. The child’s properties are applied to the parent’s objects.

Answered by Scott Driscoll

Solution #3

To @Scott Driscoll’s answer, I’d like to provide a javascript example of prototypical inheritance. With Object.create(), which is part of the EcmaScript 5 specification, we’ll use the classic inheritance approach.

First we create “Parent” object function

function Parent(){

}

Then, in the “Parent” object function, add a prototype.

 Parent.prototype = {
 primitive : 1,
 object : {
    one : 1
   }
}

Create a “Child” object with the “Child” function.

function Child(){

}

Assign a child prototype (have the child inherit from the parent prototype)

Child.prototype = Object.create(Parent.prototype);

Assign the “Child” prototype constructor to the right place.

Child.prototype.constructor = Child;

Add the method “changeProps” to a child prototype, which will rewrite the value of the “primitive” property in the Child object and change the value of “object.one” in both the Child and Parent objects.

Child.prototype.changeProps = function(){
    this.primitive = 2;
    this.object.one = 2;
};

Begin by introducing the Parent (father) and Child (son) objects.

var dad = new Parent();
var son = new Child();

ChangeProps method for Child (son)

son.changeProps();

Check the results.

The primitive attribute of the parent did not change.

console.log(dad.primitive); /* 1 */

The primordial property of the child has been altered (rewritten)

console.log(son.primitive); /* 2 */

The characteristics of the parent and child object.one have changed.

console.log(dad.object.one); /* 2 */
console.log(son.object.one); /* 2 */

http://jsbin.com/xexurukiso/1/edit/ is a working example.

More information about Object.create can be found at https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global Objects/Object/create.

Answered by tylik

Post is based on https://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs