Is there any AngularJS documentation providing a straightforward definitive list of ways to handle attributes in a directive with isolate scope?
The directive guide touches upon the use of = but doesn't list the other options used for binding.
So far, I'm aware of (via mixed sources):
scope: {
myAttr1: '=attr1',
myAttr2: '=?attr2',
myAttr3: '#attr3',
myAttr4: '&attr4'
},
have a look at the compile service: http://docs.angularjs.org/api/ng.$compile
The 'isolate' scope takes an object hash which defines a set of local scope properties derived from the parent scope. These local properties are useful for aliasing values for templates. Locals definition is a hash of local scope property to its source:
# or #attr - bind a local scope property to the value of DOM attribute. The result is always a string since DOM attributes are strings. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given and widget definition of scope: { localName:'#myAttr' }, then widget scope property localName will reflect the interpolated value of hello {{name}}. As the name attribute changes so will the localName property on the widget scope. The name is read from the parent scope (not component scope).
= or =attr - set up bi-directional binding between a local scope property and the parent scope property of name defined via the value of the attr attribute. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given and widget definition of scope: { localModel:'=myAttr' }, then widget scope property localModel will reflect the value of parentModel on the parent scope. Any changes to parentModel will be reflected in localModel and any changes in localModel will reflect in parentModel. If the parent scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You can avoid this behavior using =? or =?attr in order to flag the property as optional.
& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given and widget definition of scope: { localFn:'&myAttr' }, then isolate scope property localFn will point to a function wrapper for the count = count + value expression. Often it's desirable to pass data from the isolated scope via an expression and to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment(amount) then we can specify the amount value by calling the localFn as localFn({amount: 22}).
You can also look at the older documentation of directive. I believe the new one is better to understand but the older one has more meat.
http://code.angularjs.org/1.1.5/docs/guide/directive
Related
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
I have a directive twice on a page, but it keeps overwriting itself.
Meaning, the dialogId gets overwritten by the next instance of example-dialog in the template instead of keeping their own dialogIds.
angular.module('directives')
.directive('exampleDialog', ["$rootScope", "$timeout", "ngDialog", "$compile", '$templateRequest', function ($rootScope, $timeout, ngDialog, $compile, $templateRequest) {
return {
templateUrl: "/dialog.component.html",
transclude: {
button: '?dialogButton',
title: '?dialogTitle',
body: '?dialogBody',
footer: '?dialogFooter'
},
replace: true,
link: function (scope, el, attrs, ctrl, transclude) {
var dialogId = scope.blah.id; // gets overwritten - so when scope.open() gets called, the last instance ID gets used for both
scope.open = function(){
console.log(dialogId) // prints the same for both directives instead of the unique dialogId.
}
},
controller: function ($scope) {
}
}
}]);
With the following HTML:
<example-dialog>
<dialog-button>
ExampleButton1
</dialog-button>
<dialog-title ng-non-bindable>Title1</dialog-title>
<dialog-body ng-non-bindable>
<div ng-include="'template1.tpl.html'"></div>
</dialog-body>
<dialog-footer ng-non-bindable>
</dialog-footer>
</example-dialog>
<example-dialog>
<dialog-button>
ExampleButton2
</dialog-button>
<dialog-title ng-non-bindable>Title2</dialog-title>
<dialog-body ng-non-bindable>
<div ng-include="'template2.tpl.html'"></div>
</dialog-body>
<dialog-footer ng-non-bindable>
</dialog-footer>
</example-dialog>
How can I get them to save their own references while still inheriting their parents' scope?
EDIT
Someone recommended that I use scope: true which is supposed to create classic prototypal inheritance.
However, When I try that - open() from the <dialog-button> tansclusion point doesn't work now, along with any other bindings in the template.
You should isolate the scope of your directive. by adding scope:{} in your return statement.
By doing this your directive will create its own controlled scope and you will not be able to get any value from your parent scope(Basically no conflicts from parent scope).. To access value from parent controller/scope you will need to pass them explicitly.
To complet the previous answer, you can see in angular official doc ( here ) that :
The scope property can be false, true, or an object:
false (default): No scope will be created for the directive. The
directive will use its parent's scope.
true: A new child scope that prototypically inherits from its parent
will be created for the directive's element. If multiple directives on
the same element request a new scope, only one new scope is created.
{...} (an object hash): A new "isolate" scope is created for the
directive's template. The 'isolate' scope differs from normal scope in
that it does not prototypically inherit from its parent scope. This is
useful when creating reusable components, which should not
accidentally read or modify data in the parent scope. Note that an
isolate scope directive without a template or templateUrl will not
apply the isolate scope to its children elements.
Edit Like #itamar mentioned in his comment :
When I use scope:true it seems that it does keep the id in the directive (checked with going to the dom element and printing the scope). However, open() from the tansclusion point doesn't work now, along with any other bindings in the template.
The answer (from angularjs doc ) is :
The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the directive's element. These local properties are useful for aliasing values for templates. The keys in the object hash map to the name of the property on the isolate scope; the values define how the property is bound to the parent scope, via matching attributes on the directive's element:
# or #attr - bind a local scope property to the value of DOM attribute.
= or =attr - set up a bidirectional binding between a local scope property and an expression passed via the attribute attr. The expression is evaluated in the context of the parent scope.
< or < attr - set up a one-way (one-directional) binding between a local scope property and an expression passed via the attribute attr. The expression is evaluated in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name.
& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name.
Here is my directive code which works fine:
.directive('myGrid',function(){
return {
restrict:'E',
scope:{
info:'=info'
},
templateUrl : '/directiveGrid.html'
}
})
Here is a codepen demo
But if I change the scope like :
.directive('myGrid',function(){
return {
restrict:'E',
scope:{
info:'#info'
},
templateUrl : '/directiveGrid.html'
}
})
It doesn't work. Here is the second demo
The problem is when you have use # for isolate scope binding, it passes the value from attribute to directive. Basically while passing value using # it stringify that value and before passing it to directive. So value went to directive is not in a JSON format, it get stringified before reach to directive(data type changed to string).
If you do {{info}} you will see the result(which is nothing but string). It has been converted to string thats why you can't see ng-repear working
Preferred approach would be use =, that will do two things
Two way binding
It preserves data-type of value when value passes to directive.
Here's some explanation:
# binds a local/directive scope property to the evaluated value of the DOM attribute. Since attribute values are always strings, you will always end up with a string value for this property in the directive's scope when using #.
# allows a value defined on the directive attribute to be passed to the directive's isolate scope.
= binds a local/directive scope property to a parent scope property. So with =, you use the parent model/scope property name as the value of the DOM attribute. You can't use {{}}s with =.
= sets up a two-way binding expression between the directive's isolate scope and the parent scope. Changes in the child scope and propagated to the parent and vice-versa. Think of = as a combination of # and &. Screencast on = is here: https://egghead.io/lessons/angularjs-isolate-scope-two-way-binding
Yes, bidirectional binding (=) allows the local/directive scope and the parent scope to share data. "Expression binding" allows the directive to call an expression (or function) defined by a DOM attribute -- and you can also pass data as arguments to the expression or function. So, if you don't need to share data with the parent -- you just want to call a function defined in the parent scope -- you can use the & syntax.
I am wondering about the differences between declaring custom directive parameters in these three ways :
js :
app.directive('customDirective', function() {
return {
restrict: 'E',
scope : {
x : '=',
y : '=y',
z : '=?'
} ,
}
});
I am monitoring the three seems same behavior.
Can anyone help ?
The first is the usual two-way data-binding, which means if you say x : '=' the attribute on the element has to be x as well.
The second is an alternative to the first one with the difference that you can map the scope variable to a different attribute. For example you could say y: '=foo'. This maps the attribute foo to y.
With the last one you specify optional attributes, so you don't have to pass them along (not required).
For more information check the AngularJS documentation on $compile here.
TL;DR
= or =attr - set up bi-directional binding between a local scope property and the parent scope property of name defined via the value
of the attr attribute. If no attr name is specified then the attribute
name is assumed to be the same as the local name. Given and widget definition of scope: {
localModel:'=myAttr' }, then widget scope property localModel will
reflect the value of parentModel on the parent scope. Any changes to
parentModel will be reflected in localModel and any changes in
localModel will reflect in parentModel. If the parent scope property
doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION
exception. You can avoid this behavior using =? or =?attr in order to
flag the property as optional. If you want to shallow watch for
changes (i.e. $watchCollection instead of $watch) you can use =* or
=attr (=? or =*?attr if the property is optional).
Edit: I think the documentation is outdated because the exception appears to be thrown only in older revisions of the $compile source. In newer versions of AngularJS required scope properties which do not exist will be undefined.
Edit 2: As I have mentioned before that a newer version of Angular won't throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception anymore you will still get an error whenever you try to assign a value to an optional property which is not defined on the element.
= and =y are the same.
You can define different scope and html-attribute names like
scope:{
a: '=b'
}
With this you would bind the html-attribute bto the scope value a
=? is an optional binding. So the attribute isnt required
I am attempting to create a custom tag similar to the following:
<mytag type="Big" />
where type is an attribute that gets bound to the component. in such a way that it sets the text in a label, as shown below:
<label>{{type}}</label>
... (other components)...
As the documentation says, I have a controller that sets a default type:
$scope.type = "Small";
so that if I use my tag without the attribute type still gets set.
I am attempting to do binding using a directive:
angular.module('TestPage',[])
.directive('mytag',function() {
return {
restrict: 'E',
templateUrl: 'component.html',
scope: {
type: '='
}
}
});
Note that I do have the appropriate ng-app settings in my component template (ng-app="TestPage").
My problem is that the binding to type does not appear to be actually binding anything.
I have read the documentation about how to bind a variable to components using directive. According to the documentation, you can do such bindings inside a scope. Scopes apparently can contain an "object-hash" (whatever that is!) which creates something called an "isolate scope" (???). Such scopes can represent "local properties" in the following ways:
# or #attr - bind a local scope property to the DOM attribute. The result is always a string
since DOM attributes are strings. If no attr name is specified then the local name and
attribute name are same. Given and widget definition of scope: { localName:'#myAttr' }, then widget scope property localName will reflect the interpolated value of hello {{name}}. As the name attribute changes so will the localName property on the widget scope. The name is read from the parent scope (not component scope).
Huh??? What has all this to do with the proper syntax for binding?
= or =expression - set up bi-directional binding between a local scope property and the parent
scope property. If no attr name is specified then the local name and attribute name are same.
Given and widget definition of scope: { localModel:'=myAttr' }, then widget scope property localName will reflect the value of parentModel on the parent scope. Any changes to parentModel will be reflected in localModel and any changes in localModel will reflect in parentModel.
Excuse me? What is being said here???
& or &attr - provides a way to execute an expression in the context of the parent scope. If no
attr name is specified then the local name and attribute name are same. Given
and widget definition of scope: { localFn:'increment()' },
then isolate scope property localFn will point to a function wrapper for the increment() expression. Often it's desirable to pass data from the isolate scope via an expression and to the parent scope, this can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment(amount) then we can specify the amount value by calling the localFn as localFn({amount: 22}).
Now I'm totally confused! You have widget tags and some kind of related function that I have to write iin order to do the bind??? All I want is to bind a value to a label tag!
I have copied the above text from the documentation (http://docs.angularjs.org/guide/directive) to make a point: that this doco reads like the old UNIX documentation: really useful to those who already know the system, but not so helpful to beginners who are trying to develop real expertise. With all the tutorials that show how to do simple tasks in AngularJS (great for toy apps but not so good for the kinds of client- side applications I want to build), why aren't there any for the more advanced stuff???
Okay, time for me to be more constructive.
Can someone please provide some nice, simple examples of how to do the various bindings that this documentation is trying so hard to describe??? Examples that show the proper syntax for these scope statements and descriptions (in plain English) of exactly how they go back to the attribute being added to the custom tag???
Thank you for your patience and thanks in advance for any assistance.
I struggled a bit with this documentation too when first getting into angular, but I will make an attempt try to clarify things for you. First, when using this scope property, it creates an "isolated scope." All this means is that it won't inherit any properties from parent scopes, and so you don't have to worry about any collisions within the scope.
Now, the '#' notation means that the evaluated value in the attribute will automatically get bound into your scope for the directive. So, <my-directive foo="bar" /> would end up with the scope having a property called foo that holds the string "bar". You could also do something like <my-directive foo="{{bar}}" And then the evaluated value of {{bar}} will be bound to the scope. Since attributes are always strings, you will always end up with a string for this property in the scope when using this notation.
The '=' notation basically provides a mechanism for passing an object into your directive. It always pulls this from the parent scope of the directive, so this attribute will never have the {{}}. So, if you have <my-directive foo="bar" /> it will bind whatever is in $scope.bar into your directive in the foo property of your directive's scope. Any change's you make to foo within your scope will be refelected in bar in the parent scope, and vice versa.
I haven't used the '&' notation nearly as much as the other too, so I don't know it as well as those two. From what I understand, it allows you to evaluate expressions from the context of the parent scope. So if you have something like <my-directive foo="doStuff()" />, whenever you call scope.foo() within your directive, it will call the doStuff function in the directive's parent scope. I'm sure there's a lot more you can do with this, but I'm not as familiar with it all. Maybe someone else can explain this one in more detail.
If just the symbol is set in the scope, it will use the same name as the attribute to bind to the directives scope. For example:
scope: {
foo1: '#',
foo2: '=',
foo3: '&'
}
When including the directive, there would need to be the attributes foo1, foo2, and foo3. If you want a property in your scope different than the attribute name, you can specify that after the symbol. So, the example above would be
scope: {
foo1: '#bar1',
foo2: '=bar2',
foo3: '&bar3'
}
When including the directive, there would need to be the attributes bar1, bar2, and bar3, and these would get bound in the scope under properties foo1, foo2, and foo3 respectively.
I hope this helps. Feel free to ask questions with which I can clarify my answer.
Youre pretty close..
app.directive('mytag',function() {
return {
restrict: 'E',
template: '<div>' +
'<input ng-model="controltype"/>' +
'<button ng-click="controlfunc()">Parent Func</button>' +
'<p>{{controlval}}</p>' +
'</div>',
scope: {
/* make typeattribute="whatever" bind two-ways (=)
$scope.whatever from the parent to $scope.controltype
on this directive's scope */
controltype: '=typeattribute',
/* reference a function from the parent through
funcattribute="somefunc()" and stick it our
directive's scope in $scope.controlfunc */
controlfunc: '&funcattribute',
/* pass a string value into the directive */
controlval: '#valattribute'
},
controller: function($scope) {
}
};
});
<div ng-controller="ParentCtrl">
<!-- your directive -->
<mytag typeattribute="parenttype" funcattribute="parentFn()" valattribute="Wee, I'm a value"></mytag>
<!-- write out your scope value -->
{{parenttype}}
</div>
app.controller('ParentCtrl', function($scope){
$scope.parenttype = 'FOO';
$scope.parentFn = function() {
$scope.parenttype += '!!!!';
}
});
The magic is mostly in the scope: declaration in your directive definition. having any scope: {} in there will "isolate" the scope from the parent, meaning it gets it's own scope... without that, it would use the parent's scope. The rest of the magic is in the scope's properties: scope: { 'internalScopeProperty' : '=externalAttributeName' }... where the = represents a two way binding scenario. If you change that = to a # you'll see it just allows you to pass a string as an attribute to the directive. The & is for executing functions from the parent scope's context.
I hope that helps.
EDIT: Here is a working PLNKR