4

Let's say I have a directive that has a method sayHelloWorld() that I want to call from the scope from which I'm using the directive. How could I do it?

Also, making the entire thing a little bit more difficult, how would I call that method of a specific directive if I had multiple directives in the same scope?

Plunkr showing what I want to do: http://plnkr.co/edit/E6OLgnqArBx8lrw6s894?p=preview

alexandernst
  • 14,352
  • 22
  • 97
  • 197
  • you can write controller for each directive, so that one directive dont disturb other – PavanAsTechie Jun 18 '15 at 10:30
  • @PavanAsTechie I don't want a controller for each directive. I want to call different directive's methods from a single controller (the controller of the page). – alexandernst Jun 18 '15 at 10:31
  • Can you add jsfiddle so that it is easy to understand – PavanAsTechie Jun 18 '15 at 10:33
  • @PavanAsTechie Sure, give me a moment – alexandernst Jun 18 '15 at 10:34
  • you added the method in link: – PavanAsTechie Jun 18 '15 at 10:36
  • 1
    I don't know what you are trying to do, but i think it's a bad idea. This would require knowledge of the DOM element on which it is applied, which is against AngularJS philosophy. Can you explain what is the main problem ? – eroak Jun 18 '15 at 11:09
  • PLease refer this http://stackoverflow.com/questions/14539947/angularjs-access-directives-isolated-scope-from-parent-controller – PavanAsTechie Jun 18 '15 at 11:21
  • 2
    What you need to do is create a service for sayHelloWord and then inject that into your directive(s) – Donal Jun 18 '15 at 12:01
  • I think you're inverting roles. You don't want to call a directive from anything. The user will interact with your directive, then the directive will call what it needs (a service, another directive, broadcast an event, etc.). The data inside the directive it passed in to it from a controller, so the controller will already have the data - you don't need to reach inside the directive for it. – rodrigo-silveira Jun 18 '15 at 12:59
  • you can pass your controller scope to that directive and bind your sayHelloWorld() to that scope. now you can call it from your controller – muneebShabbir Jun 18 '15 at 12:59
  • 1
    But directives are DOM and controllers should be DOM-agnostic. Whatever you're trying to do, you shouldn't be trying to get a handle of an elment from within a controller. – Petr Skocik Jun 18 '15 at 13:06

2 Answers2

3

What you want to do is create a service that exposes your APIs, then inject the service where you need to use it (note, I'm typing this off my head, so there might be a syntax error here and there):

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

/**
 * This service exposes one API method: saveDataToServer,
 * which is a methods that takes a string, and saves it
 * to some backend server.
 */
app.service('myService', function($http){

    /**
     * Save a string to some url.
     *
     * @param {string} str
     * @return Promise
     */
    this.saveDataToServer = function(str){
       return $http.post('/url/here/', {str: str});
    };
});

/**
 * This directive uses the service above, and calls 
 * the service's API method inside its onAction method,
 * which only this directive can access directly.
 */
app.directive('myDirective', function(){
    return {
        restrict: 'ea',
        scope: {
        },
        templateUrl: '/my-template.html',
        controller: function(myService){

            /**
             * This is a method that you can call from this
             * directive's template.
             */
            $scope.onAction = function(){

                // Inside this method, make a call to the service
                myService.saveDataToServer($scope.message).
                   then(function(res){
                       // data saved!
                   });
            };
        }
    };
});

// The template (/my-template.html)
<div>
    <input type="text" ng-model="message">
    <button ng-click="onAction()">Save</button>
</div>

// Your main template (/index.html)
<my-directive></my-directive>

You could even reuse your service inside a different controller, and use the service without a directive:

app.controller('MyOtherController', function(myService){
    $scope.onSomeAction = function(str){
        myService.saveDataToServer(str).
           then(function(res){
               // data saved!
           });
    };
});

// Some template under the scope of MyOtherController
<div ng-controller="MyOtherController">
    <button ng-click="onSomeAction('hello world')">Hello</button>
</div>

Now, if all you want is to check the data inside your directive from a controller, you can just bind data from the controller into the directive. The data in the controller will change automatically when the user changes the directive. You could set up a watcher in the controller's $scope to trigger events when the directive changes the data, or pass a callback from the controller to the directive, so the directive can tell the controller that data has changed:

app.controller('MyController', function($scope){
    $scope.colors = [
       {color: 'Red'},
       {color: 'Green'},
       {color: 'Blue'}
    ];
});

app.directive('MyDirective', function(){
    return {
        scope: {
            colors: '='
        },
        templateUrl: '/my-template.html'
    };
});

// Template file: my-template.html
<div>
   <select ng-options="opt.color for opt in colors">
</div>

// index.html
<my-directive colors="colors"></my-directive>

What you might also consider, depending on your requirements, is to have the directive take the data from the controller, then handle all of the logic inside itself, instead of sending the data back to the controller and let the controller handle any logic.

For example, if your directive needs to take some input from the user and that data needs to be saved on a database, don't send the data back from the directive back to the controller, and from the controller to the server. Simply send it straight from the directive to the server. Then you can reuse the directive without rewriting the logic to send the data to a server.

rodrigo-silveira
  • 12,607
  • 11
  • 69
  • 123
  • This isn't quite what I'm asking for. That way I can't call methods inside the directive. If I did this, I'd be moving code from the directive to a service, which is different. I need to access the directive's element/internal methods. – alexandernst Jun 18 '15 at 12:36
  • What do you mean you can't call methods inside the directive? Your directive's template is calling ```$scope.onAction```, which is a method defined in, and only accessible by the directive controller. That method then fires up a reusable call to the service's ```saveDataToServer``` method. – rodrigo-silveira Jun 18 '15 at 12:38
  • But @alexandernst doesn't want directive -> controller but controller -> directive – eroak Jun 18 '15 at 12:40
  • What @Fieldset said. Just to make it simpler to understand. Imagine my directive is a dropdown and I want to call `api.setSelectedIndex(4);` from my controller. – alexandernst Jun 18 '15 at 12:44
1

One of solution is to pass an object of the controller (eg: exposedAPI) in an attribute of your directive. Then, the directive will fill this object with the exposed functions.


Plunkr


Controller

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

app.controller('MainCtrl', function($scope, $timeout) {

  $scope.exposedAPI = {};

  //Later in the code...
  $timeout(function() {

    $scope.exposedAPI.elmt_1.sayHello();

    //or

    $scope.exposedAPI.elmt_2.selectIndex(42);

    //or

    $scope.exposedAPI.elmt_3.changeDirectiveContent('Yeah !');

  });

});

Directive

app.directive("byteme", function() {
  return {
    scope: {
      'api': '='
    },
    template: '<div>{{content}}</div>',
    link: function(scope, element, attrs) {

      scope.content = 'Byteme directive!'

      //Exposed functions
      scope.api[attrs.id] = {
        sayHello: function() {

          console.log("Hello World !");

        },
        selectIndex: function(index) {

          console.log("Selected index: "+index);

        },
        changeDirectiveContent: function(newContent) {

          scope.content = newContent;

        }
      };

    }
  }
});

HTML

<div byteme id="elmt_1" api="exposedAPI"></div>
<div byteme id="elmt_2" api="exposedAPI"></div>
<div byteme id="elmt_3" api="exposedAPI"></div>

<div ng-init="exposedAPI.elmt_1.changeDirectiveContent('YEAHHH')"></div> <!-- You can call API from the view directly -->
eroak
  • 1,017
  • 10
  • 17
  • That is not a bad idea at all. Could I somehow make `getExposedAPI` an object that holds a few functions instead of being a pointer to a single function? – alexandernst Jun 18 '15 at 12:07
  • 4
    That controller is doing the job of a service object. What happens when you try to reuse those directives inside a different controller? Will every controller need to expose those same properties on the scope (```getExposedAPIFunc3```, etc.)? If you declare those inside a service, then you can just inject the service into any directive you want, as well as in any controller that may also need the APIs. – rodrigo-silveira Jun 18 '15 at 12:12
  • @rodrigo-silveira This sounds even better. Can you make a plunkr example? – alexandernst Jun 18 '15 at 12:13
  • @alexandernst I've added an answer with the code. Basically, don't use the ```link``` function when declaring your directive. Instead, just declare a controller, and pass in your service as a dependency. – rodrigo-silveira Jun 18 '15 at 12:23
  • Ok. Is the new plunkr answers your question ? – eroak Jun 18 '15 at 12:33
  • @Fieldset I think we're almost there :) Can I somehow make the `api` attribute assignable from the directive, so I can do `scope.api.hi = scope.hi = function() { ... };` instead of calling a setter `scope.api()('hi', function(){ ... });`? – alexandernst Jun 18 '15 at 12:52
  • That controller is still doing the job of a service. Why is a controller exposing an API? If you want to use those same APIs in a different controller, how exactly do you do that? Copy all that code? The API should be defined in a service object. Try replacing what you're calling ```exposedAPI``` with a service object. – rodrigo-silveira Jun 18 '15 at 13:25
  • I see what you are doing but if you are going to have an API shouldn't the directive be in complete control over placing it on the controller scope? I'm thinking of the way the ng-form works. A controller gains access to the form API by providing a unique name for the form. That name becomes the object on controller scope that the API hangs off of. – user1821052 Jun 18 '15 at 13:30
  • @rodrigo-silveira: I think, you don't understand. First of all, this is not the controller who exposes the API but the directive. So, let's imagine the directive is displaying tabs and you need a way to change the tab that is displaying from the controller ; this code can do that. – eroak Jun 18 '15 at 13:33
  • You're correct that it works. But not efficiently because you can't reuse that in a different project. Wouldn't it make more sense to have a TabsService that keeps track of which tab is currently selected, then have any controllers that need to know about the tabs just reuse the same service?! – rodrigo-silveira Jun 18 '15 at 13:37
  • I think it is the Directive itself to know which tab is currently displayed. And you can reuse this directive anywhere you want. This directive allows you controlling the directive itself – eroak Jun 18 '15 at 13:45
  • Yes, this is exactly what I was working for. Kudos for understanding from the beginning what I want ;) – alexandernst Jun 18 '15 at 13:53