Passing parameters to scope function from directive inside ng-repeat
Solution 1
Certainly for something like this the compile function isn't needed. In fact, since 1.2 the compile function is almost never needed. The only directive I've seen that MIGHT have needed the compile function is the ng-repeat directive itself, and I'm not even convinced that's true.
One important thing to remember is that when passing in a function to a directive to get called, when that function is specified in the html, THAT's when the parameters are bound. So given the following controller (note that I renamed the function being called to be more explicit)
app.controller("AppCtrl", function($scope) {
$scope.raiseNotification = function(user) {
alert(user.name)
}
....
and the following directive:
app.directive("myDir", function($compile) {
return {
restrict: 'E',
scope: {
user: '=',
click: '&'
},
template: '<a ng-click="click()" ><br>{{user.name}}</a>'
};
});
if you want to call this method then the following HTML is what you want
<div ng-repeat="user in users">
<my-dir user="user" click="raiseNotification(user)" ></my-dir>
</div>
when click is called (note that the click() in the template of the directive doesn't have its own parameter. that's because click() in your template is essentially a wrapper for calling raiseNotification(user) i.e.
function click() {
raiseNotification(user);
}
on the off chance that you want to call the click function from within your directive somewhere and override what user was bound to, then you can do that like this:
click({user:myOtherUser})
and that will override what the "user" parameter is bound to. This is a pretty edge case.
Here's an adjustment to pixelbit's code that includes a fix with the user parameter not specified in the template of the directive.
Also, just for fun, here's another version that more closely approximates what you're doing but using the link function. It's not as succinct or elegant as the previously given code, but gives a little more control using the link function in case you need to do something more specific with the events on the node.
Solution 2
The issue is likely caused when you try to re-compile/re-link a directive that has already been compiled or is in the process of compiling. When you try to re-compile / re-link a directive, this is a major red flag that you are doing something you shouldn't be.
Rather than fight the framework, and get it to do obscure things that it wasn't meant to do, try to work within the framework, and you'll find that accomplishing these tasks is a lot easier to do.
First, here is a working demo.
Tip 1: If you're recompiling a directive - don't. This has a lot of pitfalls, and is more trouble than its worth. Re-organize your HTML instead so you don't have to:
<div ng-controller="AppCtrl">
<div ng-init="user = {name: 'Works', id: 0}">
<my-dir user="user" click="click(user)"></my-dir>
</div>
<div ng-repeat="user in users">
<my-dir user="user" click="click(user)" ></my-dir>
</div>
</div>
Tip 2: If you're using isolated scope, make use of templates within your directive. Templates can leverage the isolated scope, and can help to make your directive more modular and re-usable.
app.directive("myDir", function($compile) {
return {
restrict: 'E',
scope: {
user: '=',
click: '&'
},
template: '<a ng-click="click(user)" ><br>{{user.name}}</a>'
};
});
Tip 3: Don't use 'replace', because its deprecated. Instead, use an element directive if you have to.
Angular does impose some conventions on the HTML format that you should follow, and that may mean that your HTML has some extra tags that you don't want in your final HTML. But I've learned that it's a small price to pay given the productivity gains.
Jim Cooper
I work at Pluralsight.com and love it. There's something awesome about working as a developer for a company that provides online developer training videos. I have been working in the industry for about 19 years. I love TDD and learning how to improve the way I write code. @jimthecoop
Updated on June 07, 2022Comments
-
Jim Cooper almost 2 years
I am trying to call a function from a directive and pass a parameter. The callback function is being passed in to the isolate scope. I have two problems. First, this doesn't work at all when nested inside an ng-repeat and second, even when not in ng-repeat it I don't know how to pass a parameter to the callback function. Here is a plunker showing the problem: http://plnkr.co/edit/3FN0o3UE99wsmUpxMe4x?p=preview
Notice that when you click on "This works", it at least executes the function from the parent scope, but when clicking on the others, it does nothing (because they're inside an ng-repeat). That's the first problem.
The second problem is that when you click on "This works", even though it successfully calls the function, I can't figure out how to pass along the user from the directive scope (notice that it alerts undefined).
Here is a sample directive (much simplified from my real-world application):
var app = angular.module('plunker', []); app.controller("AppCtrl", function($scope) { $scope.click = function(user) { alert(user) } $scope.users = [{ name: 'John', id: 1 }, { name: 'anonymous' }]; }); app.directive("myDir", function($compile) { return { scope: { user: '=', click: '&' }, compile: function(el) { el.removeAttr('my-dir'); el.attr('ng-click', 'click(user)') var fn = $compile(el); return function(scope, el){ fn(scope); }; } }; });
Here is the html:
<!DOCTYPE html> <html ng-app="plunker"> <head> <meta charset="utf-8" /> <title>AngularJS Plunker</title> <script>document.write('<base href="' + document.location + '" />');</script> <link rel="stylesheet" href="style.css" /> <script data-require="[email protected]" src="http://code.angularjs.org/1.2.12/angular.js" data-semver="1.2.12"></script> <script src="app.js"></script> </head> <body> <div ng-controller="AppCtrl"> <a my-dir user="{name: 'Works', id: 0}" click="click()">This works</a> <a my-dir user="user" click="click()" ng-repeat="user in users"><br>{{user.name}}</a> </div> </body> </html>
Thanks!
-
Joseph Eames over 9 yearsyou don't need the "user" parameter in your template. This works: template: '<a ng-click="click()" ><br>{{user.name}}</a>'
-
Joseph Eames over 9 yearsincluding the user parameter in your template like that is actually a bit misleading. It does nothing but looks like it does.
-
Joseph Eames over 9 yearsI can only assume that's a joke, as it truly does nothing. See the following code which works just perfectly plnkr.co/edit/sL01BCEtU5uaA5i8MnBU?p=preview using this for a template: template: '<a ng-click="click(completelyIrrelevantValue)" ><br>{{user.name}}</a>'
-
pixelbits over 9 yearsNow that's silly. I'm going to raise that an issue for the Angular team to fix.
-
Salivan over 7 yearsI think this is the most accurate and comprehensive explanation of how to pass parameters to methods bound to directives in AngularJS I've seen on the internet. Thank you! :)