I have directive in which I have scope mentioned as :
return {
restrict: 'AE',
transclude: true,
scope: {
model: '=',
menu: '='
},}
so my question is apart from model and menu variables what all variables are accessible to me? Does Isolated scope inherits parent scope variables ?
If I don't want to use isolated scope then what could be done where I can set these two variables in directives and inherits all variables from parent scope ?
Thank you.
If I understood correctly, what you want to achieve is a new scope inheriting all the parent stuff. That is easy to do.
By default a directive uses the parent scope, but you can do two different things:
scope: {} // Isolated scope
or:
scope: true // new scope inheriting from parent
So with the later, you will have all the parent scope has but you can set new stuff there and the parent won't know. Example:
http://plnkr.co/edit/oL5ALPvkEzkiXSuOSNnE?p=preview
I hope this is what you asked for.
EDIT: I edited the plunker. The idea with new scopes that inherits from others is:
If parent has a primitive like name the child will inherit it, but if you do something like:
childScope.name = ".." you are not modifying the parent name, you are shadowing it, AKA creating a new name that will hide the parent name. this means that the parent will never know if the child modified the name.
foo is something created in the new child scope, that means that the parent will never know.
user is not a primitive, it is an object and when the child modifies its name, it is not shadowing the entire user, it is just modifying its value:
childScope.user.name = "Fox"
This is getting the user reference and modifying its value, is not modifying the entire user, so you are not shadowing it. If you do:
childScope.user = { name: 'Foo' };
Then you're creating an entire user, AKA you're creating a new reference so that will shadow the parent user and the parent-child relationship of that object will end.
This is what we call the dot rule.
If the scope property of the directive definition is set to an { /* object hash */ } an isolated scope is created for your directive. If you set the scope property to true a new scope is generated that inherits from the parent scope.
See this plunker for a demo on directive scope behaviour.
If you don't want to isolate the scope but do want to pass argument to the directive you can use the $observe method of the attributes service.
function linkingFn(scope, elm, attrs, ctrl) {
// get the attribute value
console.log(attrs.ngModel);
// change the attribute
attrs.$set('ngModel', 'new value');
// observe changes to interpolated attribute
attrs.$observe('ngModel', function(value) {
console.log('ngModel has changed value to ' + value);
});
}
Creating a two way binding without an isolated scope can be done with the $parse service.
when you set scope like this, it's an isolated scope, and as its name says, it doesn't inherit any property from parent scope.
However, you can directly define variable on the scope, in the directive link method, like this:
.directive('someDirective', function(){
return {
restrict: 'C',
link: function(scope, element, attrs){
scope.newVariable = "newVariable"
}
}
}
it adds newVariable to your scope.Hope it helps~
More info on directive can be seen here, http://docs.angularjs.org/guide/directive. There is a section with the name Creating a Directive that Manipulates the DOM
Related
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.
I am reading ng-book-r27.
There are some something i can't understand well.
About 'Scope Option' of The chapter 'Directives Explained'.
First confusion:
If multiple directives on an element provide an isolate scope, only
one new scope is applied. Root elements within the template of a
directive always get a new scope; thus, for those objects, scope is
set to true by default.
I think that mean other directives will use the isolate scope as theirs.
is that right?
Second confusion:
example of inherited scope
ng-init has higher priority than custom directive.
Why the expression of ng-init will use the scope of custom dirctive.
I find a recommendation from offical doc about ng-init:
This directive can be abused to add unnecessary amounts of logic into
your templates. There are only a few appropriate uses of ngInit, such
as for aliasing special properties of ngRepeat, as seen in the demo
below; and for injecting data via server side scripting. Besides these
few cases, you should use controllers rather than ngInit to initialize
values on a scope.
OK, I can ignore the strange behavior of second confusion.
I have not read the book ng-book, but as far as I know, the statement of your first confusion does not conform with the AngularJS documentation regarding inherited and isolated scopes within a directive.
The statement above is simply not possible, having multiple directives that each have their own isolated scope would produce a $compile:multidir error. Here is a DEMO.
.directive('elem1', function($rootScope) {
return {
restrict: 'A',
scope: {}
};
})
.directive('elem2', function() {
return {
restrict: 'A',
scope: {}
}
});
After scanning the AngularJS documentation, there is no supporting statement that validates the statement:
If multiple directives on an element provide an isolate scope, only
one new scope is applied.
The closest statement I see that is similar to the statement above is the scope: true option definition when creating directives:
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.
The new scope rule does not apply for the root of the template since
the root of the template always gets a new scope.
The statement above suggests that when multiple directives with scope: true option (not an isolated scope) resides in a single element, it would create one scope and everything else is a shared scope. DEMO
.directive('elem1', function($rootScope) {
return {
restrict: 'A',
scope: true,
link: function(scope) {
console.log(scope.hello);
}
};
})
.directive('elem2', function() {
return {
restrict: 'A',
scope: true,
link: function(scope) {
scope.hello = 'world';
console.log(scope.hello);
}
}
});
You would notice that both directives logs 'world', this obviously supports the statement above.
If you read more in the $compile scope AngularJS documentation, you would see that:
In general it's possible to apply more than one directive to one
element, but there might be limitations depending on the type of scope
required by the directives. The following points will help explain
these limitations. For simplicity only two directives are taken into
account, but it is also applicable for several directives:
no scope + no scope => Two directives which don't require their own scope will use their parent's scope
child scope + no scope => Both directives will share one single child scope
child scope + child scope => Both directives will share one single child scope
isolated scope + no scope => The isolated directive will use it's own created isolated scope. The other directive will use its parent's scope
isolated scope + child scope => Won't work! Only one scope can be related to one element. Therefore these directives cannot be applied to the same element.
isolated scope + isolated scope => Won't work! Only one scope can be related to one element. Therefore these directives cannot be applied to the same element.
Perhaps you're having problems identifying the difference between prototypically inherted scopes and isolated scopes. You might want to read the $rootScope.Scope $new() method, the isolate parameter definition.
First question:
I think that mean other directives will use the isolate scope as
theirs. is that right?
The answer is an absolute no, in reference towards multiple directives with isolated scope, it would produce the $copile:multidir error.
For your second question:
ng-init has higher priority than custom directive. Why the expression
of ng-init will use the scope of custom directive?
As for directives that do not have isolated scopes or directives that don't prototypically inherit from their parent scope, you can directly associate those directives as having a scope property definition with a falsey scope value:
falsy: No scope will be created for the directive. The directive will
use its parent's scope
If a directive is bound to an element with a scope of its own then it simply uses the scope of that element, otherwise it seeks all the scope instances within the scope chain until it reaches the $rootScope.
I have the following link function in an AngularJS directive:
link: function(scope, iElement, iAttrs) {
scope.$watch('name', function(newVal){
if(newVal){
console.log(newVal);
}
}, true);
}
The full fiddle is located here: http://jsfiddle.net/balteo/K4t7P/55/
I am trying to figure out why the $watch function is not invoked when a user changes the name variable in the textarea.
You are creating a new scope when you write this on your directive
scope: {
name: '='
}
Just remove it and all will work well
Fiddle
Explanation
About the scope attribute, in the docs, we read:
If set to {} (object hash), then a new "isolate" scope is created. The 'isolate' scope differs from normal scope in that it does not prototypically inherit from the parent scope. This is useful when creating reusable components, which should not accidentally read or modify data in the parent 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.
Note that for two-way data-binding, is expected that you pass your model as attribute.
When you do this (write your model as an attribute) it works like a charm (check this fiddle).
But you are passing your attribute via ng-model. It's already available on the scope of the directive. When you create a new scope, you are actually creating a child scope at your controllers scope and setting it to your scope parameter in the link function. In fact, if you watch the $scope.$parent.name it will work as well (check this fiddle).
Can anyone explain whats the difference between accessing directive attribute from the scope as in :
scope: { someVar: '=' }
vs
link: function (scope, elem, attr, ctrl) {
attr.someVar
}
It seems somehow both accomplish the same thing.
From the AngularJS ng.$compile docs:
= 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... Any changes to parentModel will be reflected in localModel and any changes in localModel will reflect in parentModel.
In other words, this creates a watch on the attribute, causing changes to the model in either the parent scope or the directive scope to be reflected in the other.
Referencing the attribute merely returns a string. Thus if you had a directive foo and your HTML looked like
<foo some-var="bar"></foo>
In your link function, attr.someVar would be the string literal "bar". You can evaluate bar in the scope by calling scope.$eval(attr.someVar) (or scope.$parent.$eval(attr.someVar) if the directive has an isolate scope) at any time, but it is not done for you.
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