Joe Lewis
Joe Lewis

Reputation: 978

$emit or $broadcast events from service and listen them in a controller (or several)

I have coded a simple example where I want to emit/broadcast an event from a service and I want that event to be listened by a controller and change the UI, but I can't make it work and debugging the code it seems to stop in the listener but it doesn't execute the function.

http://plnkr.co/edit/eglcq7zELLfKp86DYzOe?p=preview

service:

angular.module('ServiceModule', []).
service('servicetest', ['$rootScope', function($rootScope){
    this.test = function(){
      $rootScope.$emit('testevent');
    };
}]);

controller

angular.module('ControllerModule', ['ServiceModule']).
    controller('ControllerTest', ['$scope','$rootScope','servicetest', function($scope, $rootScope, servicetest){
      $scope.name = 'World';
      servicetest.test();
      $rootScope.$on('testevent', function(){
        $scope.name = 'Joe';
      });
    }]);

index

<!DOCTYPE html>
<html ng-app="ControllerModule">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="[email protected]" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js" data-semver="1.2.16"></script>
    <script src="controller.js"></script>
    <script src="service.js"></script>
  </head>

  <body ng-controller="ControllerTest">
    <p>Hello {{name}}!</p>
  </body>

</html>

Solution:

As ivarni or Walter brand reported, the call to the service function which triggers the event has to be placed after the listener, if not you are triggering an event which doesn't have a listener able to listen yet.

We need just to change the controller as follows:

service

angular.module('ControllerModule', ['ServiceModule']).
        controller('ControllerTest', ['$scope','$rootScope','servicetest', function($scope, $rootScope, servicetest){
          $scope.name = 'World';
          $rootScope.$on('testevent', function(){
            $scope.name = 'Joe';
          });
          servicetest.test();
        }]);

Upvotes: 2

Views: 9863

Answers (3)

Gabriel C. Troia
Gabriel C. Troia

Reputation: 3330

The solution I found to work best in a medium sized app is to create a wrapper around the $emit function, and to delay the $emit with a few milliseconds, just enough for all the events to be registered.

You can also introduce some more goodies into the wrapper, like passing it your current $scope, or let it create a new child scope of the $rootscope(which will be just one level underneath so it will still be fast when propagating up to $rootScope), and use that as the data channel, event destruction after the scope dies, etc...

Here's the entire source code: http://jsfiddle.net/gabrielcatalin/2uRr7

And here's an excerpt:

/**
 * Waits a given amount of time before calling emitting
 * This works well when the event registration happens before emitting.
 *
 * @param name
 * @param fn
 * @param wait
 */
this.delayEmit = function (name, fn, wait) {
    // Because of the $scope's lifecycle the $emit(publish) happens after the $on(subscription)
    //  therefore that needs to be taken care of with a delay
    $timeout(function () {
        self.emit(name, fn);
    }, wait || 100);
}

Upvotes: 2

Walter Brand
Walter Brand

Reputation: 689

Just place the servicetest.test() below your listener in your controller, like this:

  $rootScope.$on('testevent', function(){
    $scope.name = 'Joe';
  });
  servicetest.test();

You attach the listener just after calling test(), and there for just misses the event.

Upvotes: 2

ivarni
ivarni

Reputation: 17878

You're triggering the event before you've attached the listener.

Try this:

  $rootScope.$on('testevent', function(){
    $scope.name = 'Joe';
  });
  servicetest.test();

Upvotes: 4

Related Questions