167

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?

user2707026
  • 1,679
  • 2
  • 11
  • 5

7 Answers7

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

AlwaysALearner
  • 43,759
  • 9
  • 96
  • 78
  • 1
    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? – user2707026 Aug 23 '13 at 06:57
  • 2
    The `replace` attribute has been deprecated in AngularJS: https://stackoverflow.com/questions/24194972/why-is-replace-deprecated-in-angularjs – cdmckay Aug 11 '14 at 03:34
  • 8
    for some reason the argument is undefined for me. – chovy Mar 09 '15 at 05:55
  • 1
    @chovy 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 there – marksyzm Jul 17 '15 at 13:57
  • Yes this works better and is clearer than my solution – steve Jul 07 '16 at 00:42
  • 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: http://sidewaysforward.com/2013/05/20/angularjs-directive-scope-properties/ – Yulian Oct 12 '16 at 07:13
  • I didn't understand how this works. Why pass an object with the parameter names as key? Is this angular-only behavior? – Pietro Coelho Mar 15 '17 at 18:34
  • 2
    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. – Brian Jun 21 '17 at 15:38
  • 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. – Ganesh Jadhav Jun 19 '18 at 06:46
165

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/

Juve
  • 10,584
  • 14
  • 63
  • 90
steve
  • 3,230
  • 1
  • 19
  • 14
  • 11
    Not sure how I can get a down vote for something that works ?? You should leave a comment if going to down vote. – steve Apr 04 '15 at 02:31
  • 8
    This worked for me. If you don't want the extra function just write `ng-click="updateFn()('Directive Args')"` – Graham Walters Jul 29 '15 at 10:33
  • 8
    Awwww ! scope.updateFn()("Directive Args"); !! NOT scope.updateFn("Directive Args"); !!! – Phung D. An Aug 24 '15 at 17:55
  • 3
    This is more perfect answer indeed !! – vinesh Sep 12 '15 at 18:40
  • 1
    @steve can you explain why I need to write "()" two times while caling scope.updateFn? – Piotrek Feb 03 '16 at 19:20
  • 13
    @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. HTH – steve Feb 05 '16 at 03:10
  • 2
    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. – Jan Peša Mar 16 '16 at 12:03
  • I think you need to add `controller: 'testCtrl'` to your directive's return obj. – Faust Jul 01 '16 at 17:58
  • 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? – jediz Jan 03 '17 at 12:20
  • 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');` – Rian Mar 25 '17 at 23:33
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.

Ofer Segev
  • 5,094
  • 2
  • 22
  • 22
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.

Márton Tamás
  • 2,759
  • 1
  • 15
  • 19
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!

Jane
  • 159
  • 2
  • 10
4

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

  • 4
    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 `&` binding – JorgeGRC Sep 10 '18 at 09:45
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"}.