I'm trying to use directive on ng-repeat items each with an isolate scope but it isn't working. I'm looping through each item and coloring it red with the inboxuser-select directive. However, when I put the directive on, it doesn't show any of my scope values. What is the issue here? Thanks
html file
<li class="inbox-chatter" data-ng-
repeat="inboxuser in inboxusers">
<p inboxuser-select selected={{inboxuser}}">{{inboxuser}}</p>
</li>
directive.js
.directive('inboxuserSelect', function() {
return {
restrict: 'A',
scope: {
selected: "#"
},
link: function(scope, element, attrs) {
scope.selected.css('color','red');
}
}
});
The problem is that once you set an isolate scope on the directive then the whole DOM element has that isolate scope. So the inboxuser from your ng-repeat is no longer in scope when data binding occurs (it's on the parent scope).
One option is to set scope to true instead of using an isolate scope so you'll inherit everything from the parent scope.
Or you can stick with an isolate scope, but pass inboxuser in to the directive and display it using a template. Since you're already passing inboxuser in to the directive's scope through selected it'd be easy to just add this to your directive:
template: '{{selected}}',
Also, by the way, you're missing a quote on your <p>. So this might work better for you (note I also removed {{inboxuser}} from within the <p> assuming you'll be using the template to display that instead):
<p inboxuser-select selected="{{inboxuser}}"></p>
To be honest, I don't understand what you really need to do but I have a feeling that this design will not get you there.
However, I fixed your example just for the purposes of explaining how things work.
You can see it live here.
So... when you write:
scope: {
selected: "#"
}
you are actually saying that my isolated scope will hold a single property named selected which will be of type string and will contain whatever {{inboxuser}} evaluates to. And not only this, whenever inboxuser changes in the outter scope, selected will also change in the inner, isolated scope. This is how '#' binding works.
Whatever you put nested in <p inboxuser-select selected="{{inboxuser}}"></p>, is binded to that isolated scope, which does not have an inboxuser property. So, it has to change to:
<p inboxuser-select selected="{{inboxuser}}">{{selected}}</p>
Finally, scope.selected.css('color','red'); should be changed to:
element.css('color','red');
The element argument in link function is the DOM element where the directive instance is applied. scope.selected is just a string.
I suggest you rething your overall design. If you need help, feel free to ask.
If it helps you, you can use AngScope, a tiny firebug extention i've written. It's just a quick way to inspect $scope instances associated to DOM elements inside firebug's DOM inspector.
Related
hey I have a question
If in directive I use scope {}. Then I know that is create local scope. but could someone tell me how it looks. Because I have a problem with understanding it.
It is so that If i add dom attribute, for example card
<div modal-window-card card="card" ng-model="text"></div>
Then whole variable with $scope.card is assigned to this card where next, I can modificate everything in the directive like in function yes?
But why I can't write all value from variable in :
link: function(card) {
console.log(card)
},
And one more thing, whole directive is like in new local scope yes? I mean, all functions, which are located in the directive is in this scope yes?
for example
<buton ng-click="fefe()">start</buton>
If i click start buton it will execute function with the directive, yes? if i set ng-click="$parent.fefe()" then after click it will execute function with parent scope, yes?
You are using link incorrectly. The signature is link(scope, element, attrs). As for the other questions, yes and yes, assuming your buton is within the directive's template.
I have an attribute directive 'my-isolate-directive' that decorates the value of the HTML element to which it is attached. I made it an isolate scope so I can pass params to it.
But I'd also like to continue referencing objects in the parent scope - e.g. 'outerVar' below - from within the value of the element that has the attribute directive:
<div>
'outerVar' shows! {{outerVar}}
</div>
<div my-isolate-directive my-isolate-directive-param="requiredParam">
Oops! 'outerVar' does not show! {{outerVar}}
</div>
Is this possible?
It isn't. The purpose of the isolated scope is exactly to prevent what you're trying to achieve, by not inheriting from any other scope. If you wan't to access the parent scopes and still create a new one you should set scope: true.
As #Andrés says in his answer, scope: true lets your directive have a new scope inherited from the parent.
I will add that if you would like to have attributes without isolate scope, you still can by using something like:
attr.$observe('myIsolateDirectiveParam', function(myIsolateDirectiveParam){
scope.myIsolateDirectiveParam = scope.$eval(myIsolateDirectiveParam);
});
so if found this very interesting bug in angular.js. if you have a custom directive inside a ng-repeat that is actively changing the variables in the directive don't update. meaning if i have 3 elements in my array for ng-repeat it initializes just fine but if i delete element 1 from the array any variables that element 1 had passed to its child directive somehow end up in element 2's child directive here is my example code.
<div ng-app='testing'>
<div ng-controller='testing as test'>
<div ng-repeat='item in test.example track by $index'>
{{item.title}}
<child scope='item.data'></child>
<button ng-click="test.delete($index)">
Delete
</button>
</div>
</div>
</div>
then in my js file
console.log('hello world');
var app=angular.module('testing',['testingChild']);
app.controller('testing',[function(){
this.example=[{
title:"this is the first title",
data:"this is the first index"
},{
title:"this is the second title",
data:"this is the second index"
},{
title:"this is the third title",
data:"this is the third index"
}];
this.delete=function(index){
this.example.splice(index,1);
};
}]);
var child=angular.module('testingChild',[]);
child.directive('child',[function(){
return{
restrict:"E",
scope:{
parent:"=scope"
},
template:"<div>{{child.parent}}</div>",
controller:['$scope',function($scope){
this.parent=$scope.parent;
}],
controllerAs:"child"
};
}]);
and i have a functioning jsfiddle here. all you have to do to see it work is delete one of the first elements. does anyone know what causes this and how to fix it?
Side note:
I thought it might be useful also to mention that when using this in a slighty different situation with editable elements in the child (like a text box) the data binding worked from the child to the parent. so assigning a variable attached to the controller to the scoped variable from the parent worked in that direction. this seems to be the only situation i have come across where it would be from the parent to the child and that is what is not working.
Change:
template:"<div>{{child.parent}}</div>",
controller:['$scope',function($scope){ this.parent=$scope.parent; }]
To:
template:"<div>{{parent}}</div>"
controller:function(){ }
since you are using controllerAs syntax, you dont need the $scope injection.
For the binding work as expected, you dont use child.parent, only parent (or whatever you inject in the this context on your controller
I found a property in the $compile service that fixes this problem. adding the attribute bindToController:true to the directive takes all of the variables defined in your scope attribute and attaches them to the controller rather then the scope itself meaning the 2 way data binding is to the variable on the controller rather then the variable on the scope. so the end result has these changes
in your directive definition
scope:{
parent:"=scope"
},
bindToController:true,
and in the controller remove the this.parent=$scope.parent
here is an updated jsfiddle
I have an element that has both a controller and a directive with an isolate scope applied to it:
scope: {
dirVar: '='
}
The goal is to run certain parts of the directive only if a variable holds true. I'm setting that variable in the controller and trying to pass it into the directive through an attr.
The problem is that when I do something like
<div ng-controller="MyCtrl" my-directive active="ctrlVar"></div>
and try to get active in the directive with scope.active, it always comes up undefined.
Here is an example: http://jsfiddle.net/u3t2u/1/
Any explanation as to why or how to properly do this? I assume the problem is with the controller and directive being applied to the same element and wish to get around that.
Another option would be to remove the directive's isolate scope and have it evaluate an attr passed to it, but I'm not sure how to do that ($parse keeps throwing errors).
That is because your directive is not inside the controller. Try this:
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<div my-directive="" active="myValue">
Testing.
</div>
</div>
</div>
Ended up changing the way I structured the directive because it wasn't something that should really have had an isolate scope, and the only reason it did was so it could take expressions and evaluate them to true or false.
So I changed it to use $parse, which left the directive looking something like:
var active = $parse(attrs.isActive);
// Evaluate contents of attrs.isActive
// as if they are variables within its scope,
// which is inherited from parent scopes
if(active(scope)) {
// do something
}
I am not too familiar with certain things like transclude and creating an isolated scope, but this is what I got after reading the docs for Directives and fiddling around:
http://jsfiddle.net/u3t2u/4/
I only changed this portion of the html:
<div ng-controller="MyCtrl">
<div my-directive active="myValue">
Testing.
</div>
</div>
I believe that in this case, you do not actually have to pass a value to the my-directive directive, since you are already using an isolate scope with an =. Sorry if my explanation is not that good. You can read more at http://docs.angularjs.org/guide/directive , under the section Writing directives (long version).
I'm not sure if this is actually possible, but I'm essentially wanting a reverse of the '&' isolate scope in AngularJS. Here is a Plunkr to demonstrate.
Basically, I have a custom directive set up that delivers some reusable HTML. It makes use of ng-transclude to allow some external content to be rendered within it. However, I have found a situation where I would like to access a function that has been set up on the isolate scope for the directive, from within the transcluded section of code.
So essentially I have something that looks like:
<div ng-controller="MainController">
<!-- The directive -->
<div some-custom-directive>
<!-- Button 1 that invokes a method within the controller scope -->
<button id="button1" ng-click="invoke1()">Invoke from Controller</button>
<!-- Button 2 that invokes a method on the isolate scope for the custom directive -->
<button id="button2" ng-click="invoke2()">Invoke from Isolate Scope</button>
</div>
</div>
Does anyone know if this is indeed possible?
Update
As per #Mark Rajcok's answer, the $$prevSibling found on the $scope can be used to access the isolate scope of the directive from within the transcluded content. However, I have updated the above Plunkr to attempt this from within an ng-repeat directive, which does not work. I am assuming the items within that repeat do not inherit the scope.
Although possible, the solution I present is a hack, as it uses a scope internal variable, $$prevSibling.
Inside your transcluded content, you are inside the transcluded scope. The directive's isolate and transcluded scopes are siblings. To get from the transcluded scope to the isolate scope, you can use $$prevSibling. (To get from the isolate scope to the transcluded scope, you can use $$nextSibling.)
So, this hack will work:
Invoke the Directive Action
To call a method on the controller scope, you need to specify that using & as #ganaraj already demonstrated:
<content-presenter controller-action="action()">
Then in your directive:
scope: { controllerAction: '&' },
template: ... +
'<button ng-click="controllerAction()">Trigger Ctrl action()</button>' ...
Plunker
With ng-repeat (see Samuel's comment), each item creates a child scope of the transcluded scope. In the picture below, I only show one item to keep the picture smaller. Reverse the $$nextSibling brown arrow for $$prevSibling.
So the hack to get to method action() defined on the isolate scope from an ng-repeat child scope is
<div ng-repeat="item in items" ng-click="$parent.$$prevSibling.action()">
Update for Angular v1.3+:
Since Angular v1.3, the transcluded scope is now a child of the directive's isolate scope – they are no longer siblings. So to get from the transcluded scope to the isolate scope, use $parent.
With ng-repeat, each item still creates a child scope of the transcluded scope:
But to get to method action() defined on the isolate scope from an ng-repeat child scope, we now use
<div ng-repeat="item in items" ng-click="$parent.$parent.action()">
Here is a plunk that solves your current problem. I am not sure what you are attempting to do. But as far as I know, there is no way of calling something in the isolate scope from an external scope. Ofcourse, you could setup a two way bound variable between the isolate scope and the external scope, change the variable in the external scope and $watch for it on the isolate scope ( this will work like an eventing mechanism )... That is one way of doing what you are attempting to do.. if you insist on it.
Alternatively, there is a mechanism to call a function on the external scope from the isolate scope. Its kind of like a callback.
See this http://plnkr.co/edit/5MT4vo9qXtV6nQikfEiH?p=preview
It is my understanding that adding ng-repeat to an element creates a new scope, in order for the repeating content to be correctly bound. You may need an additional $parent in that chain, such as $parent.$$nextSibling, in order to step up to the level that is adjacent to the directive's isolate scope.