Angularjs – Passing parameters to scope function from directive inside ng-repeat

angularjsangularjs-directiveangularjs-ng-repeat

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="angular.js@1.2.x" 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!

Best Answer

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.