55

I've seen people doing this from wherever in their code:

$rootScope.$broadcast('someEvent', someParameter); 

and then in some controller:

$rootScope.$on('someEvent', function(event, e){ /* implementation here */ });

Now, I'd like to broacast an event from a directive. Is it good practice to broadcast it at rootScope level ? I would like to handle this event in a controller. Can I use $scope, or do I still have to listen on $rootScope ?

John Slegers
  • 45,213
  • 22
  • 199
  • 169
Sam
  • 13,934
  • 26
  • 108
  • 194

3 Answers3

82

In my case, I just want to broadcast an event from a directive to the controller of the view, in which I use the directive. Does it still make sense to use broadcast then?

I would have the directive call a method on the controller, which is specified in the HTML where the directive is used:

For a directive that uses an isolate scope:

<div my-dir ctrl-fn="someCtrlFn(arg1)"></div>

app.directive('myDir', function() {
  return {
    scope: { ctrlFn: '&' },
    link: function(scope, element, attrs) {
       ...
       scope.ctrlFn({arg1: someValue});
    }

For a directive that does not use an isolate scope:

<div my-dir ctrl-fn="someCtrlFn(arg1)"></div>

app.directive('myDir', function($parse) {
  return {
    scope: true,  // or no new scope -- i.e., remove this line
    link: function(scope, element, attrs) {
       var invoker = $parse(attrs.ctrlFn);
       ...
       invoker(scope, {arg1: someValue} );
    }
dev.e.loper
  • 35,446
  • 76
  • 161
  • 247
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • actually, I'm doing that already. But I wanted to use event broadcasting as I thought it'd be better practice. Is it not in this case ? $emit and $on sound good :) – Sam Apr 24 '13 at 19:35
  • @Sam, I don't know which is better practice. An event results in more coupling between your directive and your controller because your controller has to listen for a specific event that your directive will emit. I prefer using a method because it is more explicit/declarative that the directive will be communicating with the controller, and I can declare in my HTML how that happens. – Mark Rajcok Apr 24 '13 at 19:43
  • 13
    @MarkRajcok I wouldn't say an event results in coupling. An event amounts more to a pub/sub architecture, which is a loosely coupled scenario. In other words, the event handler wouldn't be called if the event is never triggered, but it's not going to cause an error. Likewise with the '&' controller method, if it's not called, it's not called. I'd say the only advantage to the '&' method over the event is it doesn't "consume" an event name (which probably isn't a big deal). – Ben Lesh Jul 30 '13 at 12:56
  • 3
    Thanks @blesh. "Coupling" was a poor word choice on my part. Either the controller needs to know an event name, or the controller needs to know an attribute name (for specifying a callback function), so the advantage I mentioned doesn't really exist. – Mark Rajcok Jul 30 '13 at 19:37
  • @MarkRajcok - Can you please check this [**post**](http://stackoverflow.com/questions/24992005/angular-js-passing-value-from-directive-to-controller?noredirect=1#comment38855922_24992005)? – Unknown User Jul 28 '14 at 13:27
  • 1
    A benefit of callbacks over events is more directly observable /documented interaction between components as observed in the markup. Events have a place but can be overused and lead to code interactions that are difficult to recognize and understand. Events between sibling directives/controllers does create dependencies. Using events does not inherently reduce coupling/dependencies. Children should not be aware of siblings or parents. By flowing information (events, bound data, etc.) up to a parent and down to another child, components remain reusable and more resilient to change. – Richard Collette Oct 08 '15 at 14:03
13

It's usually a good idea not to use the $rootScope as it's global and you shouldn't pollute it unless you really know what you're doing. I would recommend that you read this article about communication between services, directives and controllers.

joakimbl
  • 18,081
  • 5
  • 54
  • 53
  • 1
    according to the article: "Whenever you have an event take place in your application which affects all controllers and directives". In my case, I just want to broadcast an even from a directive to the controller of the view, in which I use the directive. Does it still make sense to use broadcast then ? – Sam Apr 24 '13 at 18:58
  • 1
    no you should not be broadcasting ,broadcasting is meant to send events from parent scope to all child scopes and u are doing other way around – Ajay Beniwal Apr 24 '13 at 19:00
  • 7
    Yes, if you want to listen to events from a parent controller you should do `$scope.$emit` in the directive and `$scope.$on` in the parent controller – joakimbl Apr 24 '13 at 19:06
  • I don't believe rootScope works in this way anymore, you may want to update your answer for newer version of angular – Dan Beaulieu Nov 16 '15 at 21:43
0

Here is a TypeScript example of how to call back a method on the controller from an embedded directive. The most important thing to note is that the directive's parameter name for your callback uses a & when defined, and when calling that callback you should not use positional parameters but instead use an object with properties having the names of the parameters in the target.

Register the directive when you create your app module:

module MyApp {
    var app: angular.IModule = angular.module("MyApp");
    MyApp.Directives.FileUploader.register(app);
}

The registration code is as follows:

module MyApp.Directives.FileUploader {
  class FileUploaderDirective implements angular.IDirective {
      public restrict: string = "E";
      public templateUrl: string = "/app/Directives/FileUploader/FileUploaderDirective.html";

      //IMPORTANT - Use & to identify this as a method reference
      public scope: any = {
        onFileItemClicked: "&"
      };
      public controller: string = "MyApp.Directives.FileUploader.Controller";
      public controllerAs: string = "controller";
      public bindToController: boolean = true;
      public transclude: boolean = true;
      public replace: boolean = true;
  }

  export function register(app: angular.IModule) {
      app.controller("MyApp.Directives.FileUploader.Controller", Controller);
      app.directive("fileUploader", () => new FileUploaderDirective());
  }
}

The directive's controller would look like this

module MyApp.Directives.FileUploader {
    export class Controller {
        public files: string[] = ["One", "Two", "Three"];
        //The callback specified in the view that created this directive instance
        public onFileItemClicked: (fileItem) => void;

        // This is the controller method called from its HTML's ng-click
        public fileItemClicked(fileItem) {
            //IMPORTANT: Don't use comma separated parameters,
            //instead use an object with property names to act as named parameters
            this.onFileItemClicked({
                fileItem: fileItem
            });
        }
    }
}

The directive's HTML would look something like this

<ul>
  <li ng-repeat="item in controller.files" ng-click="controller.fileItemClicked (item)">
    {{ item }}
  </li>
</ul>

The main view will have an instance of your directive like so

<body ng-app="MyApp" ng-controller="MainController as controller">
  <file-uploader on-file-item-clicked="controller.fileItemClicked(fileItem)"/>
</body>

Now all you need on your MainController is a method

public fileItemClicked(fileItem) {
  alert("Clicked " + fileItem);
}
Peter Morris
  • 20,174
  • 9
  • 81
  • 146
  • 1
    Used this as a template to call a parent controller method from a nested directive 2 levels deep. Much obliged. p.s. I think you have a typo in the directive's HTML - it should be controller.fileItemClicked not controller.onFileItemSelected. – The Dumb Radish Feb 21 '17 at 16:44
  • @TheDumbRadish glad it was of help, and thanks for pointing out my code error! – Peter Morris Feb 22 '17 at 14:45