javascript - What are the nuances of scope prototypal / prototypical inheritance in AngularJS? -
the api reference scope page says:
a scope can inherit parent scope.
the developer guide scope page says:
a scope (prototypically) inherits properties parent scope.
so, child scope prototypically inherit parent scope? there exceptions? when inherit, normal javascript prototypal inheritance?
quick answer:
child scope prototypically inherits parent scope, not always. 1 exception rule directive scope: { ... } -- creates "isolate" scope not prototypically inherit. construct used when creating "reusable component" directive.
as nuances, scope inheritance straightfoward... until need 2-way data binding (i.e., form elements, ng-model) in child scope. ng-repeat, ng-switch, , ng-include can trip if try bind primitive (e.g., number, string, boolean) in parent scope inside child scope. doesn't work way people expect should work. child scope gets own property hides/shadows parent property of same name. workarounds are
- define objects in parent model, reference property of object in child: parentobj.someprop
- use $parent.parentscopeproperty (not possible, easier 1. possible)
- define function on parent scope, , call child (not possible)
new angularjs developers not realize ng-repeat, ng-switch, ng-view, ng-include , ng-if create new child scopes, problem shows when these directives involved. (see this example quick illustration of problem.)
this issue primitives can avoided following "best practice" of always have '.' in ng-models – watch 3 minutes worth. misko demonstrates primitive binding issue ng-switch.
having '.' in models ensure prototypal inheritance in play. so, use
<input type="text" ng-model="someobj.prop1"> <!--rather <input type="text" ng-model="prop1">` --> l-o-n-g answer:
javascript prototypal inheritance
also placed on angularjs wiki: https://github.com/angular/angular.js/wiki/understanding-scopes
it important first have solid understanding of prototypal inheritance, if coming server-side background , more familiar class-ical inheritance. let's review first.
suppose parentscope has properties astring, anumber, anarray, anobject, , afunction. if childscope prototypically inherits parentscope, have:

(note save space, show anarray object single blue object 3 values, rather single blue object 3 separate gray literals.)
if try access property defined on parentscope child scope, javascript first in child scope, not find property, in inherited scope, , find property. (if didn't find property in parentscope, continue prototype chain... way root scope). so, these true:
childscope.astring === 'parent string' childscope.anarray[1] === 20 childscope.anobject.property1 === 'parent prop1' childscope.afunction() === 'parent output' suppose this:
childscope.astring = 'child string' the prototype chain not consulted, , new astring property added childscope. this new property hides/shadows parentscope property same name. become important when discuss ng-repeat , ng-include below.

suppose this:
childscope.anarray[1] = '22' childscope.anobject.property1 = 'child prop1' the prototype chain consulted because objects (anarray , anobject) not found in childscope. objects found in parentscope, , property values updated on original objects. no new properties added childscope; no new objects created. (note in javascript arrays , functions objects.)

suppose this:
childscope.anarray = [100, 555] childscope.anobject = { name: 'mark', country: 'usa' } the prototype chain not consulted, , child scope gets 2 new object properties hide/shadow parentscope object properties same names.

takeaways:
- if read childscope.propertyx, , childscope has propertyx, prototype chain not consulted.
- if set childscope.propertyx, prototype chain not consulted.
one last scenario:
delete childscope.anarray childscope.anarray[1] === 22 // true we deleted childscope property first, when try access property again, prototype chain consulted.

angular scope inheritance
the contenders:
- the following create new scopes, , inherit prototypically: ng-repeat, ng-include, ng-switch, ng-controller, directive
scope: true, directivetransclude: true. - the following creates new scope not inherit prototypically: directive
scope: { ... }. creates "isolate" scope instead.
note, default, directives not create new scope -- i.e., default scope: false.
ng-include
suppose have in our controller:
$scope.myprimitive = 50; $scope.myobject = {anumber: 11}; and 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 generates new child scope, prototypically inherits parent scope.

typing (say, "77") first input textbox causes child scope new myprimitive scope property hides/shadows parent scope property of same name. not want/expect.

typing (say, "99") second input textbox not result in new child property. because tpl2.html binds model object property, prototypal inheritance kicks in when ngmodel looks object myobject -- finds in parent scope.

we can rewrite first template use $parent, if don't want change our model primitive object:
<input ng-model="$parent.myprimitive"> typing (say, "22") input textbox not result in new child property. model bound property of parent scope (because $parent child scope property references parent scope).

for scopes (prototypal or not), angular tracks parent-child relationship (i.e., hierarchy), via scope properties $parent, $$childhead , $$childtail. don't show these scope properties in diagrams.
for scenarios form elements not involved, solution define function on parent scope modify primitive. ensure child calls function, available child scope due prototypal inheritance. e.g.,
// in parent scope $scope.setmyprimitive = function(value) { $scope.myprimitive = value; } here sample fiddle uses "parent function" approach. (the fiddle written part of answer: https://stackoverflow.com/a/14104318/215945.)
see https://stackoverflow.com/a/13782671/215945 , https://github.com/angular/angular.js/issues/1267.
ng-switch
ng-switch scope inheritance works ng-include. if need 2-way data binding primitive in parent scope, use $parent, or change model object , bind property of object. avoid child scope hiding/shadowing of parent scope properties.
see angularjs, bind scope of switch-case?
ng-repeat
ng-repeat works little differently. suppose have in our controller:
$scope.myarrayofprimitives = [ 11, 22 ]; $scope.myarrayofobjects = [{num: 101}, {num: 202}] and 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> for each item/iteration, ng-repeat creates new scope, prototypically inherits parent scope, but assigns item's value new property on new child scope. (the name of new property loop variable's name.) here's angular source code ng-repeat is:
childscope = scope.$new(); // child scope prototypically inherits parent scope ... childscope[valueident] = value; // creates new childscope property if item primitive (as in myarrayofprimitives), copy of value assigned new child scope property. changing child scope property's value (i.e., using ng-model, hence child scope num) not change array parent scope references. in first ng-repeat above, each child scope gets num property independent of myarrayofprimitives array:

this ng-repeat not work (like want/expect to). typing textboxes changes values in gray boxes, visible in child scopes. want inputs affect myarrayofprimitives array, not child scope primitive property. accomplish this, need change model array of objects.
so, if item object, reference original object (not copy) assigned new child scope property. changing child scope property's value (i.e., using ng-model, hence obj.num) does change object parent scope references. in second ng-repeat above, have:

(i colored 1 line gray clear going.)
this works expected. typing textboxes changes values in gray boxes, visible both child , parent scopes.
see difficulty ng-model, ng-repeat, , inputs , https://stackoverflow.com/a/13782671/215945
ng-controller
nesting controllers using ng-controller results in normal prototypal inheritance, ng-include , ng-switch, same techniques apply. however, "it considered bad form 2 controllers share information via $scope inheritance" -- http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/ service should used share data between controllers instead.
(if want share data via controllers scope inheritance, there nothing need do. child scope have access of parent scope properties. see controller load order differs when loading or navigating)
directives
- default (
scope: false) - directive not create new scope, there no inheritance here. easy, dangerous because, e.g., directive might think creating new property on scope, when in fact clobbering existing property. not choice writing directives intended reusable components. scope: true- directive creates new child scope prototypically inherits parent scope. if more 1 directive (on same dom element) requests new scope, 1 new child scope created. since have "normal" prototypal inheritance, ng-include , ng-switch, wary of 2-way data binding parent scope primitives, , child scope hiding/shadowing of parent scope properties.scope: { ... }- directive creates new isolate/isolated scope. not prototypically inherit. best choice when creating reusable components, since directive cannot accidentally read or modify parent scope. however, such directives need access few parent scope properties. object hash used set two-way binding (using '=') or one-way binding (using '@') between parent scope , isolate scope. there '&' bind parent scope expressions. so, these create local scope properties derived parent scope. note attributes used set binding -- can't reference parent scope property names in object hash, have use attribute. e.g., won't work if want bind parent propertyparentpropin isolated scope:<div my-directive>,scope: { localprop: '@parentprop' }. attribute must used specify each parent property directive wants bind to:<div my-directive the-parent-prop=parentprop>,scope: { localprop: '@theparentprop' }.
isolate scope's__proto__references object. isolate scope's $parent references parent scope, although isolated , doesn't inherit prototypically parent scope, still child scope.
for picture below have
<my-directive interpolated="{{parentprop1}}" twowaybinding="parentprop2">,
scope: { interpolatedprop: '@interpolated', twowaybindingprop: '=twowaybinding' }
also, assume directive in linking function:scope.someisolateprop = "i'm isolated"
for more information on isolate scopes see http://onehungrymind.com/angularjs-sticky-notes-pt-2-isolated-scope/transclude: true- directive creates new "transcluded" child scope, prototypically inherits parent scope. transcluded , isolated scope (if any) siblings -- $parent property of each scope references same parent scope. when transcluded , isolate scope both exist, isolate scope property $$nextsibling reference transcluded scope. i'm not aware of nuances transcluded scope.
for picture below, assume same directive above addition:transclude: true
this fiddle has showscope() function can used examine isolate , transcluded scope. see instructions in comments in fiddle.
summary
there 4 types of scopes:
- normal prototypal scope inheritance -- ng-include, ng-switch, ng-controller, directive
scope: true - normal prototypal scope inheritance copy/assignment -- ng-repeat. each iteration of ng-repeat creates new child scope, , new child scope gets new property.
- isolate scope -- directive
scope: {...}. 1 not prototypal, '=', '@', , '&' provide mechanism access parent scope properties, via attributes. - transcluded scope -- directive
transclude: true. 1 normal prototypal scope inheritance, sibling of isolate scope.
for scopes (prototypal or not), angular tracks parent-child relationship (i.e., hierarchy), via properties $parent , $$childhead , $$childtail.
diagrams generated graphviz "*.dot" files, on github. tim caswell's "learning javascript object graphs" inspiration using graphviz diagrams.
Comments
Post a Comment