How to call a method defined in an AngularJS directive?

ID : 10407

viewed : 23

Tags : angularjsangularjs-directiveangularjs

Top 5 Answer for How to call a method defined in an AngularJS directive?

vote vote

96

If you want to use isolated scopes you can pass a control object using bi-directional binding = of a variable from the controller scope. You can also control also several instances of the same directive on a page with the same control object.

angular.module('directiveControlDemo', [])    .controller('MainCtrl', function($scope) {    $scope.focusinControl = {};  })    .directive('focusin', function factory() {    return {      restrict: 'E',      replace: true,      template: '<div>A:{{internalControl}}</div>',      scope: {        control: '='      },      link: function(scope, element, attrs) {        scope.internalControl = scope.control || {};        scope.internalControl.takenTablets = 0;        scope.internalControl.takeTablet = function() {          scope.internalControl.takenTablets += 1;        }      }    };  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>  <div ng-app="directiveControlDemo">    <div ng-controller="MainCtrl">      <button ng-click="focusinControl.takeTablet()">Call directive function</button>      <p>        <b>In controller scope:</b>        {{focusinControl}}      </p>      <p>        <b>In directive scope:</b>        <focusin control="focusinControl"></focusin>      </p>      <p>        <b>Without control object:</b>        <focusin></focusin>      </p>    </div>  </div>

vote vote

88

Assuming that the action button uses the same controller $scope as the directive, just define function updateMap on $scope inside the link function. Your controller can then call that function when the action button is clicked.

<div ng-controller="MyCtrl">     <map></map>     <button ng-click="updateMap()">call updateMap()</button> </div> 
app.directive('map', function() {     return {         restrict: 'E',         replace: true,         template: '<div></div>',         link: function($scope, element, attrs) {             $scope.updateMap = function() {                 alert('inside updateMap()');             }         }     } }); 

fiddle


As per @FlorianF's comment, if the directive uses an isolated scope, things are more complicated. Here's one way to make it work: add a set-fn attribute to the map directive which will register the directive function with the controller:

<map set-fn="setDirectiveFn(theDirFn)"></map> <button ng-click="directiveFn()">call directive function</button> 
scope: { setFn: '&' }, link: function(scope, element, attrs) {     scope.updateMap = function() {        alert('inside updateMap()');     }     scope.setFn({theDirFn: scope.updateMap}); } 
function MyCtrl($scope) {     $scope.setDirectiveFn = function(directiveFn) {         $scope.directiveFn = directiveFn;     }; } 

fiddle

vote vote

76

Although it might be tempting to expose an object on the isolated scope of a directive to facilitate communicating with it, doing can lead to confusing "spaghetti" code, especially if you need to chain this communication through a couple levels (controller, to directive, to nested directive, etc.)

We originally went down this path but after some more research found that it made more sense and resulted in both more maintainable and readable code to expose events and properties that a directive will use for communication via a service then using $watch on that service's properties in the directive or any other controls that would need to react to those changes for communication.

This abstraction works very nicely with AngularJS's dependency injection framework as you can inject the service into any items that need to react to those events. If you look at the Angular.js file, you'll see that the directives in there also use services and $watch in this manner, they don't expose events over the isolated scope.

Lastly, in the case that you need to communicate between directives that are dependent on one another, I would recommend sharing a controller between those directives as the means of communication.

AngularJS's Wiki for Best Practices also mentions this:

Only use .$broadcast(), .$emit() and .$on() for atomic events Events that are relevant globally across the entire app (such as a user authenticating or the app closing). If you want events specific to modules, services or widgets you should consider Services, Directive Controllers, or 3rd Party Libs

  • $scope.$watch() should replace the need for events
  • Injecting services and calling methods directly is also useful for direct communication
  • Directives are able to directly communicate with each other through directive-controllers
vote vote

62

Building on Oliver's answer - you might not always need to access a directive's inner methods, and in those cases you probably don't want to have to create a blank object and add a control attr to the directive just to prevent it from throwing an error (cannot set property 'takeTablet' of undefined).

You also might want to use the method in other places within the directive.

I would add a check to make sure scope.control exists, and set methods to it in a similar fashion to the revealing module pattern

app.directive('focusin', function factory() {   return {     restrict: 'E',     replace: true,     template: '<div>A:{{control}}</div>',     scope: {       control: '='     },     link : function (scope, element, attrs) {       var takenTablets = 0;       var takeTablet = function() {         takenTablets += 1;         }        if (scope.control) {         scope.control = {           takeTablet: takeTablet         };       }     }   }; }); 
vote vote

52

To be honest, I was not really convinced with any of the answers in this thread. So, here's are my solutions:

Directive Handler(Manager) Approach

This method is agnostic to whether the directive's $scope is a shared one or isolated one

A factory to register the directive instances

angular.module('myModule').factory('MyDirectiveHandler', function() {     var instance_map = {};     var service = {         registerDirective: registerDirective,         getDirective: getDirective,         deregisterDirective: deregisterDirective     };      return service;      function registerDirective(name, ctrl) {         instance_map[name] = ctrl;     }      function getDirective(name) {         return instance_map[name];     }      function deregisterDirective(name) {         instance_map[name] = null;     } }); 

The directive code, I usually put all the logic that doesn't deal with DOM inside directive controller. And registering the controller instance inside our handler

angular.module('myModule').directive('myDirective', function(MyDirectiveHandler) {     var directive = {         link: link,         controller: controller     };      return directive;      function link() {         //link fn code     }      function controller($scope, $attrs) {         var name = $attrs.name;          this.updateMap = function() {             //some code         };          MyDirectiveHandler.registerDirective(name, this);          $scope.$on('destroy', function() {             MyDirectiveHandler.deregisterDirective(name);         });     } }) 

template code

<div my-directive name="foo"></div> 

Access the controller instance using the factory & run the publicly exposed methods

angular.module('myModule').controller('MyController', function(MyDirectiveHandler, $scope) {     $scope.someFn = function() {         MyDirectiveHandler.get('foo').updateMap();     }; }); 

Angular's approach

Taking a leaf out of angular's book on how they deal with

<form name="my_form"></form> 

using $parse and registering controller on $parent scope. This technique doesn't work on isolated $scope directives.

angular.module('myModule').directive('myDirective', function($parse) {     var directive = {         link: link,         controller: controller,         scope: true     };      return directive;      function link() {         //link fn code     }      function controller($scope, $attrs) {         $parse($attrs.name).assign($scope.$parent, this);          this.updateMap = function() {             //some code         };     } }) 

Access it inside controller using $scope.foo

angular.module('myModule').controller('MyController', function($scope) {     $scope.someFn = function() {         $scope.foo.updateMap();     }; }); 

Top 3 video Explaining How to call a method defined in an AngularJS directive?

Related QUESTION?