Monday, July 22, 2013

AngularJS scope Inheritance-1

JavaScript prototypical inheritance -

Before talking about AngularJS scope inheritance lets have a quick recap on what is prototypical inheritance in javascript is.
We all know its possible to have parent child relationship in javascript(If you don't know, please go through the old articles under javascript section to get more insight). When we try to refer to any property in child object and if its available with child then we get the value immediately, but if we the property is not with child, then javascript try to get the value from its parent.
Let's try to understand it by example - 
say there is parent and child object. The prototype of child is parent. 
parent.p1 = 'p1';
child.c1='c1';

alert(child.c1) --> c1, directly from child object
alert(child.p1) -->p1 , from its parent

now we write child.p1 = 'cp1';
alert(child.p1); --> it will alert cp1. In other words defining the same property on child hides the parent's property.

Scope Inheritance in Angular - 
Scope inheritance is very much straight forward in angular, until we have 2 way binding to primitive types defined in parent from child. If we have such binding and child gets its own property which hides the parent property, then it causes unexpected output. This is not Angular specific, but the behavior of javascript only.

We will try to understand the scope inheritance in case of different directives defined in Angular. 
(For the detail of the functionality provided by different directives, please refer - http://softtechhelp.blogspot.in/2013/07/angular-js-inbuilt-directives.html )

ng-include
It creates a new child scope and inherits from parent. 
lets say we have following properties available in our controller scope(of index.htm) -
$scope.myPrimitive = 50;
$scope.myObject    = {aNumber: 11};

In our view we have following code -
tmpl1.htm -
<input ng-model="myPrimitive">

tmpl2 -
 <input ng-model="myObject.aNumber">

index.htm -
<div ng-include src="'/tmpl1.htm'"></div>
<div ng-include src="'/tmpl2.htm'"></div>

In our index.htm i have included both template htm using ng-include tag. If you see carefully we have 2 way binding with a primitive in tmpl1 and binding with reference type in tmpl2.
As per definition both child should create it's own scope and should inherit from parent scope. Which means both myPrimitive and myObject will be available to tmpl1 and tmpl2.

In tmpl1 i have tried to access myPrimitive and as its not been defined in current scope, hence it will be availed from its parent. In the similar way, myObject in tmpl2 will also be referred from parent scope only.

What will be the value of myPrmitive  and myObject.aNumber will be, if we type "123" and "456" in input box of tmpl1 and tmpl2?
The first impression comes is, it will be "123" and "456". But if you think about scoping, then typing any value in text box in tmpl1 will introduce a new property "myPrimitive" in child scope and parent property will remain intact. in case of myObject, in tmpl2 we are referring to myObject.aNumber. as myObject is not available in child scope, hence it will be referred from parent only and it will modify original parent's myObject.

What if we really want to modify the value of myPrmitive of parent from child input? The simplest way would be to use $parent.
So the code in tmpl1.htm would be -
<input ng-model="$parent.myPrimitive">

Here is the HTML which can demonstrate you the full example -
  <body>
    <div ng-controller="Ctrl">base page-<br />
        input 1 - <input ng-model="myPrimitive" /><br />
        input 2 - <input ng-model="myObject.aNumber" />
        <br />
        <div ng-include src="'tmpl1.html'"></div>
        <div ng-include src="'tmpl2.html'"></div>    
    </div>
  </body>

function Ctrl($scope) {
    $scope.myPrimitive = 50;
    $scope.myObject = { aNumber: 11 };
}
NOTE - in ng-include, if you are writing any html, then put single quote inside double quote to mention the html file path.

ng-repeat -
scoping in ng-repeat is slightly different. ng-repeat work on collection. So lets take an example of an array of primitives and array of javascript objects.
$scope.primitives= [ 123, 456 ];
$scope.objects = [{num: 111}, {num: 222}]
<ul><li ng-repeat="num in primitives">
       <input ng-model="num">
    </li>
<ul>
<ul><li ng-repeat="obj in objects ">
       <input ng-model="obj.num">
    </li>
<ul>

ng-repeat creates a new child scope for each iteration. And in case of primitive values, a copy of primitive is assigned to the child scope. Hence modifying the value in text box wont change anything in parent. The case will be different for objects, where original parent's  object will be modified.

Here is the HTML which can demonstrate you the full example -
    <div ng-controller="Ctrl">base page values-<br />
        primitive array items = {{primitives | json}}<br />
        object array items = {{objects | json}}
        <br />
     
        ng-repeat demo - <br />
         <ul>
            <li ng-repeat="obj in objects">
                <input ng-model="obj.num" />
            </li>
        </ul>
        <div>
             <div ng-repeat="item in primitives">
                <input ng-model="item" />
            </div>
        </div>
     
    </div>

function Ctrl($scope) {
    $scope.primitives = [123, 456];
    $scope.objects = [{ num: 111 }, { num: 222 }];
}

NOTE - If you try to type anything in the input binded with primitive, you will see you are not able to edit it. This is because, once you edit it, Angular's binding update it with the parent's value(which is still old). this is because editing child's field wont update the parent, and you will always see the old value.
For more detail on this problem, please read - 

ng-controller -
The scope inheritance will be very much same we discussed in ng-include.

ref - https://github.com/angular/angular.js/wiki/Understanding-Scopes#wiki-ngInclude

No comments:

Post a Comment