I want to know how to invoke a function residing in controller on the ng-click event of template element. I have to use this directive in many pages.Hence I need to handle the click event in respective controller pages.The below code invokes the click function (moreitemdetails) residing within the directive.I tried setting the scope as moreitemdetails: '=' . It is also not working.
I have been using the directive
app.directive('groceryList', function){
return {
restrict: 'E',
scope: {
array: '=',
listItemClick:'&',
moreitemdetails: '&',
},
templateUrl: 'list.html',
link: function(scope, element, attrs) {
scope.label = attrs.label;
scope.listItemClick=function(e){
$(e.currentTarget).find('.next-items').slideToggle('fast');
}
scope.moreitemdetails=function(name,type){
//other code
}
}
};
});
The call for directive is
<grocery-list array="items"></grocery-list>
This is the template file
<div ng-click="listItemClick($event)">
<div>
<div class="item">
<span class="item-details">
{{array[0].Item}}
</span>
<span class="down-arrow"></span>
</div>
<div class="next-items">
<ul>
<li class="item" ng-repeat="list in array">
<div class="item-details" ng-click="moreitemdetails(list.Name,list.Type)">{{list.Item}}</div>
</li>
</ul>
</div>
Is there a way to get around?
I also would like to know the use of $location within another directive. Quoting the previous example (everythin is same except the directive definition and action in moreitemdetails() )
app.ui.directive('groceryList', ['$location', function(location){
return {
restrict: 'E',
scope: {
array: '=',
listItemClick:'&',
moreitemdetails: '&',
},
templateUrl: 'list.html',
link: function(scope, element, attrs) {
scope.label = attrs.label;
scope.listItemClick=function(e){
$(e.currentTarget).find('.next-items').slideToggle('fast');
}
scope.moreitemdetails=function(name,type){
$location.path('/home/');
}
}
};
}]);
Thanks in advance
So by declaring
scope: {
array: '=',
listItemClick:'&',
moreitemdetails: '&',
},
you are creating an isolated scope for your directive. One solution might be to not declare this scope in your directive. This would mean that your ng-click="moreitemdetails(list.Name,list.Type) would trigger the function on your controllers scope.
Alternatively you could use an emit and listener. To do this, in your directive you could call:
scope.moreitemdetails=function(name,type){
var deets = {
name: name,
type: type
};
scope.emit('moreitemdetails',deets)
}
Then in your various controllers you implement:
scope.$on('moreitemdetails',function(event,details){
// do some code with your name and type
}
I'm not sure exactly what you would like to know about $location, if you could be a bit more specific I might be able to help a more.
Hope this helps in some way!
EDIT:
The directive without any scope declared would look like this:
return {
restrict: 'E',
templateUrl: 'list.html',
link: function(scope, element, attrs) {
scope.label = attrs.label;
scope.listItemClick=function(e){
$(e.currentTarget).find('.next-items').slideToggle('fast');
}
scope.moreitemdetails=function(name,type){
//other code
}
}
};
Related
I have written directives before that have an isolated scope but they are element directives
If i write a directive that is attribute based, how can i pass the isolated scope to it?
Thanks
You can do this in exactly the same way as you did with element directive, i.e. attach further attributes that pass in the properties to the isolated scope.
HTML:
<div greeting-directive my-greeting="'hello'" greet-who="'world'"></div>
JS:
angular.module("myApp").directive("greetingDirective", function(){
return {
restrict: 'A',
scope: {
myGreeting: "=",
greetWho : "="
},
template: '<p>{{ myGreeting }}, {{ greetWho }}</p>'
}
});
Sample Plunk
It is very similar to isolate scope for Element directives. Consider this example:
myApp.directive('test', function () {
return {
restrict: 'A',
scope: {test: '='},
link: function (scope, element) {
scope.$watch('test', function () {
console.log(scope.test)
});
}
};
});
I'm using the direcitve name as a scope variable.
<div test="ctrl.val">some stuff</div>
However if I didn't want to leverage the directive name like this:
myApp.directive('notTest', function () {
return {
restrict: 'A',
scope: {test: '='},
link: function (scope, element) {
scope.$watch('test', function () {
console.log(scope.test)
});
}
};
});
Then html might look like this:
<div not-test test="ctrl.val">some stuff</div>
I have a directive that displays an icon with a little cross. When the user clicks on this cross, a callback should be called.
Here's the code of the directive template:
<div class="item" title="{{name}}">
<button type="button" class="close">
<span ng-click="onDelete()">×</span>
</button>
<span class="glyphicon glyphicon-folder-open"></span>
</div>
The Javascript of the directive:
angular.module('hiStack').directive('hsItem', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'item.tpl.html',
scope: {
onDelete: '&',
title: '#'
}
};
});
Finally, the code that uses the directive:
<hs-item on-delete="deleteGroupModal = true" title="TestTitle"></hs-item>
deleteGroupModal = true is never called when I click on the cross. If I pass a function instead like deleteGroup(), it works.
How can I pass an assignment like we usually do with ng-click for example?
Thank you.
As Igor Janković said, it's better to pass a function than to write it directly on the attribute.
That said, it's possible to eval the expression passed on the attribute like this:
angular.module('hiStack').directive('hsItem', function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'item.tpl.html',
scope: {
title: '#'
},
link: function(scope, element, attrs) {
scope.onDelete = function() {
// Eval the code on the parent scope because directive's scope is isolated in this case
if (attrs.onDelete) scope.$parent.$eval(attrs.onDelete);
}
}
};
});
I'm wondering if there is a way to require the controller of a directive that exists/is nested somewhere as a common parent's child directive in AngularJS.
Directive Structure
Suppose I have the following structure for my directives:
<parent-directive>
<ul>
<li some-nested-directive ng-repeat="dir in directives"></li>
</ul>
<settings-menu></settings-menu>
</parent-directive>
Directive Definition
/*
* some-nested-directive directive definition
*/
.directive('someNestedDirective', function(){
// ...
return {
restrict: 'A',
controller: someNestedDirectiveController
};
});
/*
* settings-menu directive definition
*/
.directive('settingsMenu', function(){
return {
restrict: 'AE',
require: [], // how to require the nested-directive controller here?
link: function(scope, iElement, attrs, ctrls){
// ...
}
};
})
I've already checked out this SO question which states how to require controllers of directives that exist along the same line in a hierarchy.
But my question is regarding a way to do the same in a hierarchy of directives that NOT necessarily exist along the same line. And if this is not possible, what is a proper workaround for it. Any help would be appreciated.
EDIT
Also, can any of the prefixes for require (or a combination of them) be used to achieve the same?
One approach is to use parent directive as a way to pass references between controllers:
var mod = angular.module('test', []);
mod.directive('parent', function() {
return {
restrict: 'E',
transclude: true,
template: '<div>Parent <div ng-transclude=""></div></div>',
controller: function ParentCtrl() {}
}
});
mod.directive('dirA', function() {
return {
restrict: 'E',
template: '<div>Dir A <input type="text" ng-model="name"></div>',
require: ['dirA', '^^parent'],
link: function(scope, element, attrs, ctrls) {
//here we store this directive controller into parent directive controller instance
ctrls[1].dirA = ctrls[0];
},
controller: function DirACtrl($scope) {
$scope.name = 'Dir A Name';
this.name = function() {
return $scope.name;
};
}
}
});
mod.directive('dirB', function() {
return {
restrict: 'E',
template: '<div>Dir A <button ng-click="click()">Click</button></div>',
require: ['dirB', '^^parent'],
link: function(scope, element, attrs, ctrls) {
//let's assign parent controller instance to this directive controller instance
ctrls[0].parent = ctrls[1];
},
controller: function DirBCtrl($scope) {
var ctrl = this;
$scope.click = function() {
//access dirA controller through parent
alert(ctrl.parent.dirA.name());
};
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='test'>
<parent>
<dir-a></dir-a>
<dir-b></dir-b>
</parent>
</div>
When using above approach it also makes sense to encapsulate how the dirA controller is stored inside parent controller i.e. by using a getter property or by exposing only the required properties of dirA controller.
I aggree with miensol's reply and I recommend that approach but in some cases you may need something like that;
<parent-directive>
<ul>
<some-nested-directive id="snd1" ng-repeat="dir in directives"></some-nested-directive>
</ul>
<settings-menu some-nested-directive-id="snd1"></settings-menu>
You can access the scope of some-nested-directive using its id from the settings-menu;
$("#" + scope.someNestedDirectiveId).scope()
Once I used this approach to cascade the values of a dropdown according to the choise of another independent dropdown.
i've created a custom directive in angularjs:
directives.directive('myTop',function($compile) {
return {
restrict: 'E',
templateUrl: 'views/header.html',
}
})
Directive's code:
<div class="my-header">
<button ng-click="alert('x')" class="fa fa-chevron-left"></button>
<h1>SpeakZ</h1>
</div>
for some reason, ng-click doesen't trigger.
I searched over the internet and found that compile / link is the solution for this problem,
but I can't seem to reach a working solution.
I am not using jquery..
You'll need to add a link function to the directive definition for this to work. So basically,
var app = angular.module("myApp", [])
app.directive('myTop',function() {
return {
restrict: 'E',
template: '<button ng-click="clickFunc()">CLICK</button>',
link: function (scope) {
scope.clickFunc = function () {
alert('Hello, world!');
};
}
}
})
And the html:
<div ng-app="myApp">
<my-top></my-top>
</div>
And here's the fiddle: http://jsfiddle.net/4otpd8ah/
Either use link as answered by #Ashesh or just simply add scope. If you set scope false you will not have isolated scope and click will work on directive.
directives.directive('myTop',function($compile) {
return {
restrict: 'EA',
scope: false,
templateUrl: 'views/header.html',
}
})
I'd like to have a directive with an isolated scope, and to set properties to this scope from within the directive. That is to create some environment variables, which would be displayed by other directives inside it, like so:
HTML:
<div environment> <!-- this directive set properties to the scope it creates-->
{{ env.value }} <!-- which would be available -->
<div display1 data="env"></div> <!-- to be displayed by other directives (graphs, -->
<div display2 data="env"></div> <!-- charts...) -->
</div>
JS:
angular.module("test", [])
.directive("environment", function() {
return {
restrict: 'A',
scope: {},
link: function(scope) {
scope.env = {
value: "property set from inside the directive"
};
}
};
})
.directive("display1", function() {
return {
restrict: 'A',
require: '^environment'
scope: {
data: '='
},
link: function(scope, elt, attr, envController) {
scope.$watch('data', function(oldV, newV) {
console.log("display data");
});
}
};
})
.directive("display2", function() {
return {/* ... */};
});
But it doesn't work. Here is a Plunker.
If I remove the isolation, it works ok though. What do I do wrong ? Is it a problem of transclusion ? It seems to work if I use a template in the 'environment' directive, but this is not what I want.
Thanks for your help.
Edit: I see this same problem answered here. The proposed solution would be to use a controller instead of a directive. The reason I wanted to use a directive is the possibility to use 'require' in the inner directives, thing that can't be done with ngController I think.
By introducing external templates, I managed to find a working solution to your problem.
I'm quite certain the way you have it set up has worked at some point but I can't be certain about when. The last time I built a directive not reliant on an external markup file, I don't even know.
In any case, the following should work, if you are willing to introduce separate templates for your directives:
app.directive('environment', function () {
return {
restrict: 'A',
templateUrl: 'env.html',
replace: true,
scope: {},
link: function (scope, el, attrs) {
scope.env = {
value: "property set from inside the directive"
};
}
};
});
app.directive('display1', function () {
return {
restrict: 'A',
scope: {
data: '='
},
templateUrl: 'display1.html',
replace: false,
link: function(scope) {
// console.log(scope.data);
}
};
});
And then for your markup (these wouldn't sit in <script> tags realistically, you would more than likely have an external template but this is simply taken from the fiddle I set up).
<script type="text/ng-template" id="display1.html">
<span>Display1 is: {{data}}</span>
</script>
<script type="text/ng-template" id="env.html">
<div>
<h1>env.value is: {{env.value}}</h1>
<span display1 data="env.value"></span>
</div>
</script>
<div>
<div environment></div>
</div>
Fiddle link: http://jsfiddle.net/ADukg/5421/
Edit: After reading that you do not want to use templates (should've done that first..), here's another solution to get it working. Unfortunately, the only one you can go with (aside from a few others, link coming below) and in my opinion it is not a good looking one...
app.directive('environment', function () {
return {
restrict: 'A',
template: function (element, attrs) {
return element.html();
},
scope: {},
link: function (scope, el, attrs) {
scope.env = {
value: "property set from inside the directive"
};
}
};
});
And the markup:
<div environment> {{env.value}} </div>
Fiddle: http://jsfiddle.net/7K6KK/1/
Say what you will about it, but it does do the trick.
Here's a thread off of the Angular Github Repo, outlining your issue and why it is not 'supported'.
I did a small edit to your Plunker
When you create a variable on scope of directive other directives can access it two ways (presented in plunker) either directly or by two-way data binding
HTML:
<body ng-app="test">
<div environment>
{{ env.value }}
<div display1 data="env"></div>
<div display2 data="env"></div>
</div>
</body>
<input type="text" ng-model="env.value"> #added to show two-way data binding work
<div display1 info="env"></div> #changed name of attribute where variable is passed, it's then displayed inside directive template
<div display2>{{env.value}}</div> #env.value comes from environment directive not from display2
</div>
JS
angular.module("test", [])
.directive("environment", function() {
return {
restrict: 'A',
scope: true, #changed from {} to true, each environment directive will have isolated scope
link: function(scope) {
scope.env = {
value: "property set from inside the directive"
};
}
};
})
.directive("display1", function() {
return {
restrict: 'A',
template: '<span ng-bind="info.value"></span>', #added template for directive which uses passed variable, NOTE: dot in ng-bind, if you try a two-way databinding and you don't have a dot you are doing something wrong (Misko Hevry words)
scope: {
info: '=' #set two-way data binding for variable from environment directive passed in 'info' attribute
}, #removed unnecessary watch for variable
};
})
.directive("display2", function() {
return {/* ... */};
});