AngularJS - Exposing a Directive API

I've been doing a fair bit of AngularJS development over the past 18 months (currently full-time) and I've come across a pattern I've found my self using a fair bit. For the most part it seems to hold water and make my life easier, but I don't know if that's just a short term gain that will come back to bite me (or someone else) in the near future. I also wanted to post about it to see if it either helps any one else or invokes someone to point out why it's a bad idea.

I'm very conscious of how easy it is to generate a maintenance burden when building an application. Lots of developers, including myself, have been forced to maintain 'legacy' applications and have to work with your decisions for years.

So the pattern started from a suggestion on the angular documentation when wanting to expose an API to other directives.

This advice is pointing to the use of 'require: "^fooParent" directive and the subsequent injection on the link function like so.

app.directive('fooParent', [function () {
  return {
    restrict: 'E',
    controller: function ($scope) {
      var self = this;
      self.hello = function (name) {
        alert("hello " + name);
      }
    },
    link: function(scope, element, attrs) {
      //Some code...
    }
  }
}]);

app.directive('fooChild', [function () {
  return {
    restrict: 'E',
    require: '^fooParent'
    link: function(scope, element, attrs, fooParentCtrl) {
      //Use parent directive controller method.
      fooParentCtrl.hello("fred");
    }
  }
}]);

Once you have this, your html structure goes something like ...

<fooParent>
  <fooChild></fooChild>
</fooParent>

This is obviously a not a useful example, but should illustrate the basics around using a parent directive's controller. Once the angular $digest is processed, the alert will show "hello fred".

What I've done recently with this idea is more widely expose the controller of useful directives with the following.

app.directive('fooParent', [function () {
  return {
    restrict: 'E',
    scope: {
      controllerEmitEventName: '@'
    }
    controller: function ($scope) {
      var self = this;
      self.hello = function (name) {
        alert("hello " + name);
      }
      $scope.$emit($scope.controllerEmitEventName, self);
    },
    link: function(scope, element, attrs) {
      //Some code...
    }
  }
}]);

app.controller('fooController', ['$scope',function ($scope) {
  $scope.$on('myFooEvent',function (e,Ctrl){
    $scope.fooCtrl = Ctrl;
    $scope.fooCtrl.hello("bob");
  });
}]);

<div ng-controller="fooController">
  <fooParent controller-emit-event-name="myFooEvent"></fooParent>
</div>

When the event is listened for on another controller, you can have access to the specific instance of directive controller to fire events explicitly without using chained $braodcast/$on methods. Also requires the use of the 'scope' property on a directive enforcing an isolated scope (opens new window), which is a good thing if you are producing reusable directives.

I have found this useful when combined with the idea that no state should live on an angular service* (unless very good reason for it). The directive should hold any state required for its main purpose. Remember, there is a pretty good chance some one will ask to support multiple instances of your directive on the same page.