1
votes

How to expose directive methods without using $broadcast or '=' between modules?

Using $broadcast (events) if there are multiple directives all will be notified. It cannot return value too.

Exposing directive's function by html attribute I think it is not that best that Angular has to offer.

Angular Bootstrap UI do it using services (I guess): It have a service named "$uibModal". You can call a function "$uibModal.open()" of Modal Directive by injecting $uibModal service.

Is that the right way?

3
The release of AngularJS V1.7.1 introduces the new ng-ref directive. The ng-ref attribute tells AngularJS to publish the controller of a component on the current scope. This is useful for having a component such as an audio player expose its API to sibling components. Its play and stop controls can be easily accessed. For more information, see How to expose behavior from a directive with isolated scope?.georgeawg

3 Answers

2
votes

An example of a directive that registers its API with a service:

app.service("apiService", function() {
    var apiHash = {};
    this.addApi = function (name,api) {
        apiHash[name] = api;
    };
    this.removeApi = function (name) {
        delete apiHash[name];
    };
    this.getApi = function (name) {
        return apiHash[name];
    };
});

app.directive("myDirective", function (apiService) {
    return {
        restrict: 'E',
        scope: {},
        template: `<h1>{{title}}</h1>`,
        link: postLink
    };
    function postLink(scope, elem, attrs)
        var name = attrs.name || 'myDirective';
        var api = {};
        api.setTitle = function(value) {
            scope.title = value;
        };
        apiService.addApi(name, api);
        scope.$on("$destroy", function() {
            apiService.removeApi(name);
        });
    }
});

Elsewhere in the app, the title of the directive can be set with:

apiService.getApi('myDirective').setTitle("New Title");

Notice that the directive registers the api with a name determined by the name attribute of the directive. To avoid memory leaks, it unregisters itself when the scope is destroyed.


Update

How could I use it from a controller?

  app.controller('home', function($scope,apiService) {
    $scope.title = "New Title";
    $scope.setTitle = function() {
      apiService.getApi('mainTitle').setTitle($scope.title);
    };
  })
  <body ng-controller="home">

    <my-directive name="mainTitle"></my-directive>
    <p>
      <input ng-model="title" />
      <button ng-click="setTitle()">Set Title
      </button>
    </p>
  </body>

The DEMO

angular.module('myApp', [])
  .service("apiService", function() {
    var apiHash = {};
    this.addApi = function(name, api) {
      apiHash[name] = api;
    };
    this.getApi = function(name) {
      return apiHash[name];
    };
  })

.directive("myDirective", function(apiService) {
    return {
      restrict: 'E',
      scope: {},
      template: `<h1>{{title}}</h1>`,
      link: postLink
    };

    function postLink(scope, elem, attrs) {
      var name = attrs.name || 'myDirective';
      var api = {};
      api.setTitle = function(value) {
        scope.title = value;
      };
      apiService.addApi(name, api);
      scope.$on("$destroy", function() {
        apiService.addApi(name, null);
      });
    }
  })
  
  .controller('home', function($scope,apiService) {
    $scope.title = "New Title";
    $scope.setTitle = function() {
      apiService.getApi('mainTitle').setTitle($scope.title);
    };
  })
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="myApp" ng-controller="home">
    
    <my-directive name="mainTitle"></my-directive>
    <p>
      <input ng-model="title" />
      <button ng-click="setTitle()">Set Title
      </button>
    </p>
  </body>
0
votes
.factory('myService', [function() {
    return {
        charCount: function(inputString) {
            return inputString.length;
        }
    }
}])

this service exposes function charCount(); in your directive you have to inject it like this

.directive('testDirective', ['myService', function(myService) {
    return {
        restrict: 'A',
        replace: true,
        template: "<div>'{{myTestString}}' has length {{strLen}}</div>",
        link: function($scope, el, attrs) {
            $scope.myTestString = 'string of length 19';
            $scope.strLen       = myService.charCount( $scope.myTestString );
        }
    }
}])

and, of course call it

$scope.strLen       = myService.charCount( $scope.myTestString );
0
votes

<html>
    <style>
        #out {
            width:96%;
            height:25%;
            padding:10px;
            border:3px dashed blue;
            font-family: monospace;
            font-size: 15px;
        }
    </style>

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>

    <script>
        var APP = angular.module('MYAPP', []);

        APP.controller('main', ['$scope', '$element', '$compile', 'myService', function($scope, $element, $compile, myService) {
            $scope.test          = 'my Test Controller';
            $scope.directiveTest = "directive test";
            var testSvc = myService.charCount($scope.test);
            $scope.showTestDir = true;
        }])
        .directive('testDirective', ['myService', function(myService) {
            return {
                restrict: 'A',
                replace: true,
                template: "<div>'{{myTestString}}' has length {{strLen}}</div>",
                link: function($scope, el, attrs) {
                    $scope.myTestString = 'string of length 19';
                    $scope.strLen       = myService.charCount( $scope.myTestString );
                }
            }
        }])
        .factory('myService', [function() {
            return {
                charCount: function(inputString) {
                    return inputString.length;
                }
            }
        }])
        .filter('toUpper', function() {
            return function(input) {
                return input.toUpperCase();
            }
        })
        .filter('toLower', function() {
            return function(input) {
                return input.toLowerCase();
            }
        })
        ;
    </script>
        <body ng-app="MYAPP">
        <div id="out" ng-controller="main">
            {{test}} - not filtered
            <br/>
            {{test|toUpper}} - filtered toUpper
            <br/>
            {{test|toLower}} - filtered toLower
            <br/>
            <br/>
            <div test-directive ng-if="showTestDir"></div>
        </div>
    </body>
</html>