How do I create a custom event in an AngularJs service
Asked Answered
E

3

37

I am working on an AngularJs project. I have a service which sets and removes events on some buttons. This service is utilized by another service which I do not want to interact directly with the buttons. However I would like a button click event to be filtered up through the first service and handled in the second one. Since I don't want the second service to be aware of the buttons, I figure I will need to create a custom event in the first service. How can I create a custom event and fire it when a button is clicked?

Thanks in advance.

Effie answered 9/6, 2014 at 21:55 Comment(1)
Maybe the observer pattern bring other solutions to the problem scotch.io/bar-talk/…Bigmouth
R
85

If you want to send an event between services/directives use broadcast:

$rootScope.$broadcast('buttonPressedEvent');

And recieve it like this:

$rootScope.$on('buttonPressedEvent', function () {
             //do stuff
        })
Rubeola answered 9/6, 2014 at 22:6 Comment(3)
Hey, thank you very much. However is there for the service itself to fire the event such so that in order to handle it the second service must reference the first? For example: inside service2 there would be a line of code: service1.eventfired = do_something().Effie
Yes, that is the last part from my answer. in your second service you need to add that code, just change $scope.$on to $rootScope.$onRubeola
This is so much cleaner than what I was considering. Hooray for you, and hooray for $rootscope.$broadcast()!Trainman
G
35

Any global scope based integration would hurt namespacing and could result terrible side-effects in middle and large size applications. I recommend a bit more complicated but definitely cleaner solution, using proxy service.

1. Create proxy service

angular.module('app').service('userClickingHelper', [
  function() {
    var listeners = [];
    return {
      click: function(args) {
        listeners.forEach(function(cb) {
          cb(args);
        });
      },
      register: function(callback) {
        listeners.push(callback);
      }
    };
  }
]);

2. Create button directive which is using the userClickingHelper as a dependency.

angular.module('app').directive('onDistributedClick', ['userClickingHelper', function (userClickingHelper) {
    return {
        restrict: 'A',
        link: function (scope, element) {
            element.on('click', userClickingHelper.click);
        }
    };
}]);

3. Register your unrelated service with using the proxy.

angular.module('app').service('myUnrelatedService', [function (userClickingHelper) {
    userClickingHelper.register(function(){
        console.log("The button is clicked somewhere");
    });
}]);

The result is an isolated scope of event distribution, yet neither the directive, nor the service knows about each other. It is also easier to unit-test.

I am using similar solution for globally accessable "Loading" modal so I can abstract the way a controller/service is saying "user should not click or anything right now".

Gentile answered 9/4, 2015 at 11:40 Comment(2)
+1. This answer provides general good practice when implementing listeners / handlers whereas the answer by Chancho is more tailored to the OP's use case.Huntsville
I liked this approach as we can hook for notification from trusted source or we know from where it should come from. If we use $rootScope.$broadcast then in complex application there are high chances that someone will overlap on same event to broadcast their data.Leyte
F
8

You can create and emit an event with the following on your element

ng-click="$emit('customEvent')"

Then in your controller you can use

$rootScope.$on('customEvent', function(){
    // do something
})

Demo

Feodor answered 9/6, 2014 at 21:58 Comment(4)
I wish for two 'services' to communicate not a view and a controller. However after reading your answer I got a sort of epiphany. I could just establish a two-way communication between the services. When service 1 captures the button click event it can call the required method in service two which will do the processing I require. It seems like a simple way of achieving what I need. Thank you very much!Effie
Scratch that! I would be tightly coupling the two services together! I will use the $rootScope.$on() and $rootScope.$broadcast() method indicated by another user. Thanks again! (And in case you are wondering, yes, I am a novice)!Effie
it actually does exactly the same, my demo has broadcast and emit functions, I used emit for demonstration, my approach was to emit directly from the DOM rather than adding more logic in the controllerFeodor
This answer needs more upvotes! For my needs, $rootScope.$broadcast() is more appropriate, but I can see how in some situations, using ng-click="$emit('customEvent')" would be better.Trainman

© 2022 - 2024 — McMap. All rights reserved.