Question

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="angular.js@1.2.x" 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();
        }]);
Was it helpful?

Solution

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

Try this:

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

OTHER TIPS

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.

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);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top