Two way binding not working in directive with transcluded scope - javascript

I've a textbox in a controller which is bound to model name. There's a directive inside the controller and there's another textbox inside the directive which is bound to the same model name:
<div class="border" ng-controller="editCtrl">
Controller: editCtrl <br/>
<input type="text" ng-model="name" />
<br/>
<tabs>
Directive: tabs <br/>
<input type="text" ng-model="name"/>
</tabs>
</div>
mod.directive('tabs', function() {
return {
restrict: 'E',
transclude: true,
template:
'<div class="border" ng-transclude></div>',
};
});
When you type something in the outer textbox it's reflected in the inner textbox but if you type something in the inner textbox it stops working i.e. both textbox no more reflects the same value.
See example at: http://jsfiddle.net/uzairfarooq/MNBLd/
I've also tried using two way binding attr (scope: {name: '='}) but it gives syntax error.And using scope: {name: '#'} has same effect.
Any help would be greatly appreciated.
In addition to the accepted answer, this article really helped me in understanding the prototypical inheritance in child scpoes. I'd highly recommend anyone having problem with scopes to read it thoroughly.

A directive with transclude: true results in the directive creating a new (transcluded) child scope. This new scope prototypically inherits from the parent scope. In your case, the parent scope is the scope associated with the editCtrl controller.
Using two-way databinding in a child scope (i.e., ng-model) to bind to a parent scope property that holds a primitive value (e.g., name) always causes problems -- well, I should say that it doesn't work as expected. When the scope property is changed in the child (e.g., you type into the second textbox) the child creates a new scope property that hides/shadows the parent scope property of the same name. If the parent property holds a primitive value, that value is (essentially) copied to the child property when the child property is created. Future changes in the child scope (e.g., the second textbox) only affect the child property.
Before typing into the second textbox (i.e., before the property is changed in the child), the child/transcluded scope finds the name property in the parent scope via prototypal inheritance (dashed line in picture below). This is why the two textboxes initially remain in synch. Below, if you type "Mark" into the first text box, this is what the scopes look like:
I created a fiddle where you can examine the two scopes. Click the "show scope" link next to the second textbox before typing into the second textbox. This will allow you to see the transcluded child scope. You will notice that it does not have a name property at this point. Clear the console, type into the second text box, then click the link again. You will notice that the child scope now has a name property, and the initial value was the value that parent property had ("Mark"). If you typed " likes Angular" into the second text box, this is what the scopes look like:
There are two solutions:
do what #pgreen2 suggests (this is the "best practice" solution) -- use an object instead of a primitive. When an object is used, the child/transcluded scope does not get a new property. Only prototypal inheritance is in play here. In the picture below, assume the editCtrl's $scope has this object defined: $scope.myObject = { name: "Mark", anotherProp: ... }:
use $parent in the child scope (this is a fragile solution, and not recommended, as it makes assumptions about HTML structure): use ng-model="$parent.name" inside the <input> that is within the <tabs> element. The first picture above shows how this works.
A syntax error occurs when using scope: {name: '='} because when using two-way databinding (i.e., when using '='), interpolation is not allowed -- i.e., {{}} can't be used. Instead of <tabs name="{{name}}"> use <tabs name="name">.
Using '#' works the same as the transclude case because ng-transclude uses the transcluded scope, not the isolate scope that is created by using scope: { ... }.
For (lots) more information about scopes (including pictures) see What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

I believe that the problem has to do with scoping. Initially the inner textbox doesn't have name set, so it is inherited from the outer scope. This is why typing in the outer box is reflected in the inner box. However, once typing in the inner box occurs, the inner scope now contains name which means it is no longer bound to the outer name so the outer text box doesn't sync up.
The appropriate way to fix is only storing models in the scope, not your values. I fixed it in http://jsfiddle.net/pdgreen/5RVza/ The trick is to create a model object (data) and referencing values on it.
The incorrect code modifies the scope in the directive, the correct code modifies the model in the scope in the directive. This subtle difference allows the scope inheritance to work properly.
I believe the way Miško Hevery phrased it was, scope should be write-only in the controller, and read-only in directives.
update: reference: https://www.youtube.com/watch?v=ZhfUv0spHCY#t=29m19s

Syntax error means that you miswrote something. It is not related to a particular framework / library. You probably forgot to add "," or close a paranthesis. Check it out again

Related

How to access $scope variable in controller? [duplicate]

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?
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
<input type="text" ng-model="someObj.prop1">
<!--rather than
<input type="text" ng-model="prop1">`
-->
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 = {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 a new child scope, which prototypically inherits from the parent scope.
Typing (say, "77") into the first input textbox causes the child scope to get a new myPrimitive scope property that hides/shadows the parent scope property of the same name. This is probably not what you want/expect.
Typing (say, "99") into the second input textbox does not result in a new child property. Because tpl2.html binds the model to an object property, prototypal inheritance kicks in when the ngModel looks for object myObject -- it finds it in the parent scope.
We can rewrite the first template to use $parent, if we don't want to change our model from a primitive to an object:
<input ng-model="$parent.myPrimitive">
Typing (say, "22") into this input textbox does not result in a new child property. The model is now bound to a property of the parent scope (because $parent is a child scope property that references the parent scope).
For all scopes (prototypal or not), Angular always tracks a parent-child relationship (i.e., a hierarchy), via scope properties $parent, $$childHead and $$childTail. I normally don't show these scope properties in the diagrams.
For scenarios where form elements are not involved, another solution is to define a function on the parent scope to modify the primitive. Then ensure the child always calls this function, which will be available to the child scope due to prototypal inheritance. E.g.,
// in the parent scope
$scope.setMyPrimitive = function(value) {
$scope.myPrimitive = value;
}
Here is a sample fiddle that uses this "parent function" approach. (The fiddle was written as part of this answer: https://stackoverflow.com/a/14104318/215945.)
See also https://stackoverflow.com/a/13782671/215945 and https://github.com/angular/angular.js/issues/1267.
ng-switch
ng-switch scope inheritance works just like ng-include. So if you need 2-way data binding to a primitive in the parent scope, use $parent, or change the model to be an object and then bind to a property of that object. This will avoid child scope hiding/shadowing of parent scope properties.
See also AngularJS, bind scope of a switch-case?
ng-repeat
Ng-repeat works a little differently. Suppose we 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 a new scope, which prototypically inherits from the parent scope, but it also assigns the item's value to a new property on the new child scope. (The name of the new property is the loop variable's name.) Here's what the Angular source code for ng-repeat actually is:
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), essentially a copy of the value is assigned to the new child scope property. Changing the child scope property's value (i.e., using ng-model, hence child scope num) does not change the array the parent scope references. So in the first ng-repeat above, each child scope gets a num property that is independent of the myArrayOfPrimitives array:
This ng-repeat will not work (like you want/expect it to). Typing into the textboxes changes the values in the gray boxes, which are only visible in the child scopes. What we want is for the inputs to affect the myArrayOfPrimitives array, not a child scope primitive property. To accomplish this, we need to change the model to be an array of objects.
So, if item is an object, a reference to the original object (not a copy) is assigned to the new child scope property. Changing the child scope property's value (i.e., using ng-model, hence obj.num) does change the object the parent scope references. So in the second ng-repeat above, we have:
(I colored one line gray just so that it is clear where it is going.)
This works as expected. Typing into the textboxes changes the values in the gray boxes, which are visible to both the child and parent scopes.
See also Difficulty with ng-model, ng-repeat, and inputs and
https://stackoverflow.com/a/13782671/215945
ng-controller
Nesting controllers using ng-controller results in normal prototypal inheritance, just like ng-include and ng-switch, so the same techniques apply.
However, "it is considered bad form for two controllers to share information via $scope inheritance" -- http://onehungrymind.com/angularjs-sticky-notes-pt-1-architecture/
A service should be used to share data between controllers instead.
(If you really want to share data via controllers scope inheritance, there is nothing you need to do. The child scope will have access to all of the parent scope properties.
See also Controller load order differs when loading or navigating)
directives
default (scope: false) - the directive does not create a new scope, so there is no inheritance here. This is easy, but also dangerous because, e.g., a directive might think it is creating a new property on the scope, when in fact it is clobbering an existing property. This is not a good choice for writing directives that are intended as reusable components.
scope: true - the directive creates a new child scope that prototypically inherits from the parent scope. If more than one directive (on the same DOM element) requests a new scope, only one new child scope is created. Since we have "normal" prototypal inheritance, this is like ng-include and ng-switch, so be wary of 2-way data binding to parent scope primitives, and child scope hiding/shadowing of parent scope properties.
scope: { ... } - the directive creates a new isolate/isolated scope. It does not prototypically inherit. This is usually your best choice when creating reusable components, since the directive cannot accidentally read or modify the parent scope. However, such directives often need access to a few parent scope properties. The object hash is used to set up two-way binding (using '=') or one-way binding (using '#') between the parent scope and the isolate scope. There is also '&' to bind to parent scope expressions. So, these all create local scope properties that are derived from the parent scope.
Note that attributes are used to help set up the binding -- you can't just reference parent scope property names in the object hash, you have to use an attribute. E.g., this won't work if you want to bind to parent property parentProp in the isolated scope: <div my-directive> and scope: { localProp: '#parentProp' }. An attribute must be used to specify each parent property that the directive wants to bind to: <div my-directive the-Parent-Prop=parentProp> and scope: { localProp: '#theParentProp' }.
Isolate scope's __proto__ references Object.
Isolate scope's $parent references the parent scope, so although it is isolated and doesn't inherit prototypically from the parent scope, it is still a child scope.
For the picture below we have
<my-directive interpolated="{{parentProp1}}" twowayBinding="parentProp2"> and
scope: { interpolatedProp: '#interpolated', twowayBindingProp: '=twowayBinding' }
Also, assume the directive does this in its 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 - the directive creates a new "transcluded" child scope, which prototypically inherits from the parent scope. The transcluded and the isolated scope (if any) are siblings -- the $parent property of each scope references the same parent scope. When a transcluded and an isolate scope both exist, isolate scope property $$nextSibling will reference the transcluded scope. I'm not aware of any nuances with the transcluded scope.
For the picture below, assume the same directive as above with this addition: transclude: true
This fiddle has a showScope() function that can be used to examine an isolate and transcluded scope. See the instructions in the comments in the fiddle.
Summary
There are four types of scopes:
normal prototypal scope inheritance -- ng-include, ng-switch, ng-controller, directive with scope: true
normal prototypal scope inheritance with a copy/assignment -- ng-repeat. Each iteration of ng-repeat creates a new child scope, and that new child scope always gets a new property.
isolate scope -- directive with scope: {...}. This one is not prototypal, but '=', '#', and '&' provide a mechanism to access parent scope properties, via attributes.
transcluded scope -- directive with transclude: true. This one is also normal prototypal scope inheritance, but it is also a sibling of any isolate scope.
For all scopes (prototypal or not), Angular always tracks a parent-child relationship (i.e., a hierarchy), via properties $parent and $$childHead and $$childTail.
Diagrams were generated with graphviz "*.dot" files, which are on github. Tim Caswell's "Learning JavaScript with Object Graphs" was the inspiration for using GraphViz for the diagrams.
I in no way want to compete with Mark's answer, but just wanted to highlight the piece that finally made everything click as someone new to Javascript inheritance and its prototype chain.
Only property reads search the prototype chain, not writes. So when you set
myObject.prop = '123';
It doesn't look up the chain, but when you set
myObject.myThing.prop = '123';
there's a subtle read going on within that write operation that tries to look up myThing before writing to its prop. So that's why writing to object.properties from the child gets at the parent's objects.
I would like to add an example of prototypical inheritance with javascript to #Scott Driscoll answer. We'll be using classical inheritance pattern with Object.create() which is a part of EcmaScript 5 specification.
First we create "Parent" object function
function Parent(){
}
Then add a prototype to "Parent" object function
Parent.prototype = {
primitive : 1,
object : {
one : 1
}
}
Create "Child" object function
function Child(){
}
Assign child prototype (Make child prototype inherit from parent prototype)
Child.prototype = Object.create(Parent.prototype);
Assign proper "Child" prototype constructor
Child.prototype.constructor = Child;
Add method "changeProps" to a child prototype, which will rewrite "primitive" property value in Child object and change "object.one" value both in Child and Parent objects
Child.prototype.changeProps = function(){
this.primitive = 2;
this.object.one = 2;
};
Initiate Parent (dad) and Child (son) objects.
var dad = new Parent();
var son = new Child();
Call Child (son) changeProps method
son.changeProps();
Check the results.
Parent primitive property did not change
console.log(dad.primitive); /* 1 */
Child primitive property changed (rewritten)
console.log(son.primitive); /* 2 */
Parent and Child object.one properties changed
console.log(dad.object.one); /* 2 */
console.log(son.object.one); /* 2 */
Working example here http://jsbin.com/xexurukiso/1/edit/
More info on Object.create here https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create

When should I use inherited scope and when should I not?

When creating a directive, When should I use inherited scope (i.e scope:true) and when I should not use it (i.e scope:false )
I understand the difference between scope types and know what each scope type does. But I don't understand when I should choose one of them rather than the other.
As per my knowledge following are the decision making criteria
Controller Scope Data Change -(One direction)-> Directive Scope Data Change : Use inherited scope. Scope : True
Controller Scope Data Change <-(Both direction)-> Directive Scope Data Change: Use parent scope. Scope : False
Controller Scope Data Change -(No data change required)- Directive Scope Data Change: Use new isolated scope. Scope : { }
Suppose we have a functionality as:
{Username} text box (Outside Directive) and
Hello {Name} (inside directive, and it is being displayed after the {Username} text box).
If the functionality is like changing {Username} should reflect {Name} then use 1.
If the functionality is like changing {Name} or {Username} will reflect vice versa, then use 2.
If there is no relation between {Name} and {Username} then use 3.
Though we can use 3(isolated scope) with 3 types of prefixes (#,=,&) by which we can pass the data between scopes and this is the best approach as per my knowledge.

Does 'this' always point on the current scope in a template?

Is it always safe to use this inside a template to get a reference on the current scope?
<span ng-click="log(this)">
Is my onclick parameter always the current controllers scope?
</span>
Here is a fiddle to show what I mean.
Searched around as well, but couldn't find any point on this in the docs. It seems to work, however I don't want to depend on an undocumented feature. If you can, please link the proper doc for this.
The expression log(this) is evaluated against the scope, and scope object has a this property in it as well which points to its own reference. So it will evaluate this against the scope will yield the scope itself. For example you could also pass $parent and you will see it will point to the parent scope since the argument in the expression gets evaluated against the current scope will yield scope['$parent'] similarly it happens to scope['this'] too. Using ng-click="method(this)" in angular context completely different from doing onclick="somefunc(this)" on a DOM element. Angular is able to expand it only because it has a property with the name this attached to the scope object.
Another thing is you do not need to pass this even otherwise the method is run by evaluating against the scope object, so referring to this will yield the scope itself. i.e you could as well do ng-click="log()" and in the log method you can refer to this as the current scope where the element is bound to. For instance if you were using controllerAs syntax and you are calling:
<div ng-controller="MyOtherCtrl as vm">
<span ng-click="vm.log(this)">Click on me, if background gets teal, then this points on the child controllers scope</span>
</div>
Now this will not be the controller instance (which is what you would generally expect it to be), instead it will just be the scope object itself as it evaluates the expression as scope.vm.log(scope.this)
It would always be safer to use $scope in the context of the controller, unless you specifically want to refer to some child scope created on the view due to some directive(by some ng-repeat, ng-if etc..) wrapping that particular context. Also remember properties get prototypically inherited (except for isolated scoped directive) in the child scopes.
And there are no links documented or anything as far as i know, but source code itself is the official record. Angular uses $parse to expand the expressions and if you see the source code you can see that angular invokes the expressions with function.apply with the context.
this refers to the $scope of the controller directive.
For eg:
<div ng-controller="myCntrl">
<span ng-click="log(this)">
Is my onclick parameter always the current controllers scope?
</span>
</div>
So, this is referring to the myCntrl's $scope.

Angular directive - what if scope is not set?

How are scope attributes being inherited when scope is not set in Angular directive definition?
In Angular docs (under 'Directive Definition Object') there are two cases: when scope is true or when scope is an object ({}). What if scope is not set or is false. How are attributes inherited in this case for the scope and for it's children?
When scope is set to false(also default value) in directive definition, behavior of scope differs based on whether you are doing transclusion or not.
If you do not transclude, it pretty much uses parent scope. Just as if you did not used a directive and written the linked template directly in the page.
Fiddle
Transclusion always creates a child scope but it's bound to parent scope in a strange way when the directive scope is false. You can "read" parent scope but as soon as you "write" to a property it's not bound to parent scope's same property anymore but now a property on child scope unless that property is an object.
Transcluded Fiddle
Child scope is the ones in green border.
Try changing the parent scope first. Enter something in the "var" input field, you will see that the child scope's var will also change. But when you change var in child scope it's not changing the parent scope, and changing the var in parent scope does not affect child scope as the bond is broken when you wrote to var from child scope. This does not apply to the objects (try the same on sp.var, you will see that you cannot break the "bond"). According to developers this is the expected and/or intended behavior.
If the scope is set to false (which is the default) then the directive has the same scope as the parent- no new scope is created. Since they share a scope any changes in the parent will be reflected in the directive and vice-versa.
Since this isn't great from an encapsulation standpoint, many recommend using an isolate scope whenever possible (an isolate scope being when you set the scope to {})

AngularJS: input not binding to expected scope when inside a view

I'm having an issue with binding an input from inside a view. I thought it would bind to the controller scope, but it seems to be binding to a child scope, so it's not updating above.
Other items will bind like I expect if they're inside an ng-repeat (I'm not sure why).
Here's an example:
http://jsfiddle.net/hMpsB/1/
What's the best way to bind the input to the correct scope if it's not inside an ng-repeat?
In your example you will have better luck binding your $scope.test to an object instead of a primitive type like this:
$scope.test = { val: "test value" };
You can see this fiddle for a working example.
The child scope that gets created in the ngView will copy your value and since your original $scope.test is a primitive string it has no link to the parent value so your input will be modifying the child scope copy. When binding to an object your child scope has a copy of the object reference but will ultimately modify the same instance of the object.
You can take a look at this question for more information on creating a service to persist data across multiple controllers (which is a little similar to your question).
You can also look into using $parent as described in this answer though as Mark mentions it's undocumented and might get messy if another child scope ever gets introduced somewhere.

Categories