AngularJS - Pass assignment to a directive callback - angularjs

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);
}
}
};
});

Related

AngularJS - How to pass html attribute variable value to directive?

...
...
UPDATE
HTML
<my-directive ng-repeat="item in items = ( data | filter: {isExists: true})">
something
</my-directive>
<my-second-directive counter="{{items.length}}"></my-second-directive>
JS
angular.module('directives')
.directive('myDirective', function () {
...
})
.directive('mySecondDirective', function () {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '#'
},
template: '',
link: function (scope, element, attrs) {
alert(scope.counter);
}
});
Excuse me I've not described my question well. My first directive should be ngRepeated, with filter. But in my second directive, I would like to allow to show a counter, how many first directive is currently instantiated, because the user will be able to add and remove instances. So I would like to get the value of the items.length by fly with the second directive. But the link() method of the second directive is fired prior the ngRepeat, so the value of the counter will be an empty string.
Thanks in advance
UPDATE 2
.directive('cardGroupHeader', function($templateCache){
return {
restrict: 'EA',
transclude: true,
replace: true,
require: '^cardGroup',
scope: {
cbiscounter: '=?',
cbcounter: '=?',
cbisarrow: '#?'
},
template: $templateCache.get('card-group-header-tpl.html'),
link: function(scope, $element, $attrs, cardGroupController) {
scope.rowId = cardGroupController.getCurrentId();
console.log(scope.cbcounter);
scope.toggle = function () {
cardGroupController.toggle(scope.rowId)
}
angular.element(document).ready(function () {
console.log(scope.cbcounter);
});
scope.$watch('scope.cbcounter', function (n, o) {
if(n && n != o) {
console.log(n);
}
});
//scope.cbcounter++;
}
};
})
HTML
<card-group-header cbiscounter="true" cbarrow="true" cbcounter="data.length">Waiting for Approval</card-group-header>
<card-group-row cbCollapsed="false">
<card ng-repeat="approveItem in data = (approveItems | filter: {isApproved: false, isRejected: false})">
TEMPLATE
$templateCache.put('card-group-header-tpl.html', '<div class="card-group-header" ng-click="toggle()"><span ng-transclude></span><span class="card-group-counter" ng-if="cbiscounter">{{cbcounter}}</span></div>');
When I change the data.length to 2, this is transferred well. If I use the data.length the scope.cbcounter is always undefined. In case of 2 I've got it back on the console.log(scope.cbcounter);
The counter: '#' means that you are accepting a string value. If you wanted to pass an expression you could either use:
<my-second-directive counter="{{ items.length }}"></my-second-directive>
Or:
.directive('mySecondDirective', function () {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '=' // Accept two ways binding
},
template: '',
link: function (scope, element, attrs) {
alert(scope.counter);
}
});
EDIT:
I finally quite understand the problem! It's because of attributes are not interpolated until after the link phase. You have two following options:
The first option is wrapping every in the link inside $timeout to take it away from the event loop and be executed after DOM finished manipulating:
.directive('mySecondDirective', function ($timeout) {
return {
restrict: 'EA',
transclude: false,
replace: true,
scope: {
counter: '=' // Accept two ways binding
},
template: '',
link: function (scope, element, attrs) {
$timeout(function() {
alert(scope.counter);
});
}
});
Secondly, using $observe:
attrs.$observe('counter', function(value){
console.log(value);
});
or using $watch as #jusopi suggested.
I think this would be what you want.
Html
<div ng-app="testapp" ng-controller="testctrl">
<div ng-repeat="item in filtereditems">
{{item}}
</div>
<testdir counter="filtereditems.length" />
</div>
Javascript
angular.module('testapp', [])
.directive('testdir', function(){
return {
restrict: 'E',
scope:{
counter: '='
},
link: function(scope, element, attrs) {
alert(scope.counter);
}
}
})
.controller('testctrl', function($scope, $filter){
$scope.items = [
{name: 'A', isExists: true},
{name: 'B', isExists: false},
{name: 'C', isExists: true},
{name: 'D', isExists: false}
];
$scope.filtereditems = $filter('filter')($scope.items, {isExists: true});
})
My jsfiddle is here.
In addition to #LVarayut's answer about the scope's binding expression, the reason the alert is undefined is because linking is not part of the $digest cycle. So binding and data hasn't be effected yet (don't quote me on that, it's the best way I could verbalize what I'm showing in the code below).
Instead you need to use a watcher to trigger the alert
link: ($scope, elem, attrs)->
#undefined because linking isn't part of the $digest cycle
#alert $scope.count
$scope.$watch 'count', (n, o)->
if n and n isnt o
true
#alert n
http://plnkr.co/edit/xt95gb3cTXfUEHgpWK1W?p=preview

How do you pass in data to custom directives as a value of the attribute itself?

I have a directive defined as such:
angular.module("main.directives").directive("todo", function() {
return {
restrict: "A",
scope: {
todo: "=entity"
},
replace: false,
templateUrl: "todo.html",
link: function(scope, element, attributes) {
}
};
});
which I use like this from templates:
<div todo entity="todoData"></div>
todoData comes from a controller or some other the local scope. Anyway it all works like a charm, so that's cool!
My question is the following: How do I have to modify the directive definition so that it also works with a markup of this type:
<div todo="todoData"></div>
As you can see the data is now passed in as the value of the attribute marking the directive. Just like ng- directives do:
<p ng-repeat="bit in data"></p>
<p ng-click="whatever()"></p>
How can that be achieved?
Thanks
Replace
scope: {
todo: "=entity"
},
by
scope: {
todo: "=todo"
},
or simply
scope: {
todo: "="
},
When you write an attribute directive in angularjs you might want to have it fed by an attribute value.
For example, something like this:
<div my-attribute="somevalue"></div>
How then do you create a new scope that takes that in? It's not obvious. Any here's how you do it:
app.directive('myAttribute', function() {
return {
restrict: 'A',
scope: {
myAttribute: '='
},
template: '<div style="font-weight:bold">{{ myAttribute | number:2}}</div>'
};
});
The trick to notice is that the "self attribute" because of the name of the attribute in camel case.
Here is the Reference to This Answer!
you must eval the value of the attribute inself. The isolate scope is not one of my favorites kind of scopes for a directive. Instead you can use, scope = true, to inherit from the parent controller. This will allow you to use all the variable exposes on the parents scopes.
in your case.
angular.module("main.directives").directive("todo", function() {
return {
restrict: "A",
scope: true,
replace: false,
templateUrl: "todo.html",
link: function(scope, element, attributes) {
scope.todo = scope.$eval(attributes[todo]);
}
};
});
now your todo directive could be used. Like any other ng- directive.
example:
<div todo="getTodoList()"></div>
<div todo="[{description:'hahahha'}]"></div>

Angularjs Custom Directive ng-click not working

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',
}
})

AngularJS: how do I code an element directive with optional attribute parameters?

I'm doing my first steps in AngularJS directives. Just set up this one as an exercise to output a product name:
.directive('productname', function (Prefs) {
return {
restrict: 'E',
replace: true,
transclude: true,
templateUrl: '/partials/productname.html',
link: function (scope, element, attrs) {
scope.brand = Prefs.owner;
}
}
})
productname.html
<span class="productname"><span class="brand">{{brand}}</span> <span class="product" ng-transclude>{{productname}}</span></span>
and so the usage is plainly: <productname>{{product.name}}</productname>
Now, could someone tell me how I should go about making this directive configurable by adding a flag to output the productname linked?
The usage would be: <productname linked>{{product.name}}</productname> and the output/template would be:
<span class="productname"> <span class="brand">{{brand}}</span> <span class="product" ng-transclude>{{productname}}</span></span>
Seems complicated and I cannot quite figure out where the logic should be injected...
First of all, you should use the scope property of the directive declaration. Also, you do not need transclude in this case. Like so:
.directive('productname', function (Prefs) {
return {
restrict: 'E',
scope: {
product: "=",
linked: "="
},
replace: true,
templateUrl: '/partials/productname.html',
link: function (scope, element, attrs) {
// scope.product and scope.linked are automatically set
scope.brand = Prefs.owner;
}
}
})
And the template:
<span class="productname" ng-switch="linked">
<a href="/edit/{{id}}" ng-switch-when="true">
<span class="brand">{{brand}}</span>
<span class="product">{{product.name}}</span>
</a>
<span ng-switch-default>
<span class="brand">{{brand}}</span>
<span class="product">{{product.name}}</span>
</span>
</span>
Call the template like so:
<productname product="product"></productname>
or:
<productname product="product" linked="'true'"></productname>
Update
If you want to use the linked attribute as a flag, you could do that by using the attrs variable:
.directive('productname', function (Prefs) {
return {
restrict: 'E',
scope: {
product: "="
},
replace: true,
templateUrl: '/partials/productname.html',
link: function (scope, element, attrs) {
// scope.product is automatically set
scope.linked = typeof attrs.linked != 'undefined';
scope.brand = Prefs.owner;
}
}
})
Call it like so:
<productname product="product" linked></productname>

Single angular js directive and many controllers

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
}
}
};

Resources