AngularJS - pass function to directive
Asked Answered
J

7

170

I have a example angularJS

<div ng-controller="testCtrl">

<test color1="color1" updateFn="updateFn()"></test>
</div>
 <script>
  angular.module('dr', [])
.controller("testCtrl", function($scope) {
    $scope.color1 = "color";
    $scope.updateFn = function() {
        alert('123');
    }
})
.directive('test', function() {
    return {
        restrict: 'E',
        scope: {color1: '=',
                updateFn: '&'},
        template: "<button ng-click='updateFn()'>Click</button>",
        replace: true,
        link: function(scope, elm, attrs) { 
        }
    }
});

</script>
</body>

</html>

I want when I click button, the alert box will appear, but nothing show.

Can anyone help me?

Jamarjamb answered 22/8, 2013 at 10:56 Comment(0)
L
253

To call a controller function in parent scope from inside an isolate scope directive, use dash-separated attribute names in the HTML like the OP said.

Also if you want to send a parameter to your function, call the function by passing an object:

<test color1="color1" update-fn="updateFn(msg)"></test>

JS

var app = angular.module('dr', []);

app.controller("testCtrl", function($scope) {
    $scope.color1 = "color";
    $scope.updateFn = function(msg) {        
        alert(msg);
    }
});

app.directive('test', function() {
    return {
        restrict: 'E',
        scope: {
            color1: '=',
            updateFn: '&'
        },
        // object is passed while making the call
        template: "<button ng-click='updateFn({msg : \"Hello World!\"})'>
            Click</button>",
        replace: true,        
        link: function(scope, elm, attrs) {             
        }
    }
});

Fiddle

Leitmotif answered 22/8, 2013 at 11:1 Comment(9)
Thank Codezilla for your answer, and I want to ask about the circumstance when I want to bind the function "updateFn" from parent scope to isolate scope in directive "test", is that possible?Jamarjamb
The replace attribute has been deprecated in AngularJS: #24195472Soelch
for some reason the argument is undefined for me.Millesimal
@Millesimal I think the argument is only used once you call the method again? The first open bracket usage seems to be the format that angular wants for the method to just be passed, but I might be wrong thereCaldera
Yes this works better and is clearer than my solutionCalvados
If there's anyone else wondering how exactly this things works and why the directives have to be so tightly coupled with the parent scope (they are not), read this: sidewaysforward.com/2013/05/20/…Decorticate
I didn't understand how this works. Why pass an object with the parameter names as key? Is this angular-only behavior?Selfgratification
An object mapping updateFn({msg: 'my message'}); has to be used in that format when making the function call inside the directive's link function.Fab
Am I the only one completely flummoxed and unable to understand mostly because the same name "updateFn" is used everywhere in this solution? Can anyone please improve this answer? The distinction between names will help a lot.Zedekiah
C
168

Perhaps I am missing something, but although the other solutions do call the parent scope function there is no ability to pass arguments from directive code, this is because the update-fn is calling updateFn() with fixed parameters, in for example {msg: "Hello World"}. A slight change allows the directive to pass arguments, which I would think is far more useful.

<test color1="color1" update-fn="updateFn"></test>

Note the HTML is passing a function reference, i.e., without () brackets.

JS

var app = angular.module('dr', []);

app.controller("testCtrl", function($scope) {
    $scope.color1 = "color";
    $scope.updateFn = function(msg) {        
        alert(msg);
    }
});

app.directive('test', function() {
    return {
        restrict: 'E',
        scope: {
            color1: '=',
            updateFn: '&'
        },
        // object is passed while making the call
        template: "<button ng-click='callUpdate()'>
            Click</button>",
        replace: true,        
        link: function(scope, elm, attrs) {       
          scope.callUpdate = function() {
            scope.updateFn()("Directive Args");
          }
        }
    }
});

So in the above, the HTML is calling local scope callUpdate function, which then 'fetches' the updateFn from the parent scope and calls the returned function with parameters that the directive can generate.

http://jsfiddle.net/mygknek2/

Calvados answered 13/3, 2015 at 1:33 Comment(10)
Not sure how I can get a down vote for something that works ?? You should leave a comment if going to down vote.Calvados
This worked for me. If you don't want the extra function just write ng-click="updateFn()('Directive Args')"Grown
Awwww ! scope.updateFn()("Directive Args"); !! NOT scope.updateFn("Directive Args"); !!!Denier
This is more perfect answer indeed !!Unheardof
@Calvados can you explain why I need to write "()" two times while caling scope.updateFn?Salamone
@Ludwik11 sure - its because scope.updateFn when defined like this is a function that returns a function (hence the ()()) and this is because we pass into scope (via update-fn="updateFn" in html) a reference to the function we want called. The 1st () is a call to angular to return this reference, the 2nd () makes the call to our function and is where we pass any parameters. HTHCalvados
Be aware that above mentioned solution changes the this scope. So e.g. if you use controllerAs and you are passing function containing this.something into directive - then calling it from the directive won't work as expected.Plasmo
I think you need to add controller: 'testCtrl' to your directive's return obj.Vannoy
I don't get why you do the 'fetching' part. I pass function in html template with brackets my-func="'vm.myFunction()", not fetching anything in the controller of the sub-component and it just works in the sub-template. Could it be a nuance between controller and directive?Lunarian
Instead of using scope.updateFn()("Directive Args"); you can also do this: scope.updateFn('msg':'The actual message that you want to pass to the controller');Elliellicott
E
39

In your 'test' directive Html tag, the attribute name of the function should not be camelCased, but dash-based.

so - instead of :

<test color1="color1" updateFn="updateFn()"></test>

write:

<test color1="color1" update-fn="updateFn()"></test>

This is angular's way to tell the difference between directive attributes (such as update-fn function) and functions.

Eos answered 29/1, 2014 at 14:39 Comment(1)
thanks for the catch. I have included that in my answer. Voted up! :)Leitmotif
L
10

How about passing the controller function with bidirectional binding? Then you can use it in the directive exactly the same way as in a regular template (I stripped irrelevant parts for simplicity):

<div ng-controller="testCtrl">

   <!-- pass the function with no arguments -->
   <test color1="color1" update-fn="updateFn"></test>
</div>

<script>
   angular.module('dr', [])
   .controller("testCtrl", function($scope) {
      $scope.updateFn = function(msg) {
         alert(msg);
      }
   })
   .directive('test', function() {
      return {
         scope: {
            updateFn: '=' // '=' bidirectional binding
         },
         template: "<button ng-click='updateFn(1337)'>Click</button>"
      }
   });
</script>

I landed at this question, because I tried the method above befire, but somehow it didn't work. Now it works perfectly.

Luben answered 21/4, 2017 at 14:42 Comment(0)
W
6

use dash and lower case for attribute name ( like other answers said ) :

 <test color1="color1" update-fn="updateFn()"></test>

And use "=" instead of "&" in directive scope:

 scope: { updateFn: '='}

Then you can use updateFn like any other function:

 <button ng-click='updateFn()'>Click</button>

There you go!

Wayward answered 28/6, 2016 at 20:15 Comment(4)
Why would you use '=' instead of '&' ? when I tried this it kept on repeatedly calling my function.Flotsam
It's wrong to use '=' for this. That is for two way object binding.Ophir
I think, the only problem is the use of parentheses in the first template. This executes the function then binds the result. Instead You should pass only the name of the function, like this: update-fn="updateFn"Annabal
Bad answer. very bad.Streetman
O
4

I had to use the "=" binding instead of "&" because that was not working. Strange behavior.

Owenowena answered 21/8, 2017 at 9:11 Comment(1)
This is because you're most likely passing the directive a JS function reference instead of the execution. When you pass the function as an argument to the directive update-fn="updateFn()" you must include the parenthesis (and maybe params). Passing it as a function reference update-fn="updateFn" will not work with the & bindingIgnore
G
0

@JorgeGRC Thanks for your answer. One thing though, the "maybe" part is very important. If you do have parameter(s), you must include it/them on your template as well and be sure to specify your locals e.g. updateFn({msg: "Directive Args"}.

Ginsburg answered 12/7, 2019 at 8:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.