Sam
Sam

Reputation: 14586

AngularJS : broadcast event from directive

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 ?

Upvotes: 55

Views: 65049

Answers (3)

Mark Rajcok
Mark Rajcok

Reputation: 364677

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} );
    }

Upvotes: 82

Peter Morris
Peter Morris

Reputation: 23224

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);
}

Upvotes: 1

joakimbl
joakimbl

Reputation: 18081

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.

Upvotes: 13

Related Questions