Pergunta

Codifiquei um exemplo simples em que desejo emitir/transmitir um evento de um serviço e quero que esse evento seja ouvido por um controlador e altere a interface do usuário, mas não consigo fazê-lo funcionar e depurar o código parece parar no ouvinte, mas não executa a função.

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

serviço:

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

controlador

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

índice

<!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>

Solução:

Conforme relatado por ivarni ou Walter Brand, a chamada para a função de serviço que dispara o evento deve ser feita após o ouvinte, caso contrário você está disparando um evento que ainda não tem um ouvinte capaz de ouvir.

Precisamos apenas alterar o controlador da seguinte forma:

serviço

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();
        }]);
Foi útil?

Solução

Você está acionando o evento antes de anexar o ouvinte.

Experimente isto:

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

Outras dicas

Basta colocar servicetest.test() abaixo do seu ouvinte no seu controlador, assim:

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

Você anexa o ouvinte logo após chamar test() e simplesmente perde o evento.

A solução que descobri que funciona melhor em um aplicativo de tamanho médio é criar um wrapper em torno da função $emit e atrasar o $emit em alguns milissegundos, apenas o suficiente para que todos os eventos sejam registrados.

Você também pode introduzir mais algumas vantagens no wrapper, como passar seu $scope atual ou deixá-lo criar um novo escopo filho do $rootscope (que estará apenas um nível abaixo, então ainda será rápido ao propagar até $ rootScope) e usá-lo como canal de dados, destruição de eventos após o término do escopo, etc.

Aqui está todo o código-fonte: http://jsfiddle.net/gabrielcatalin/2uRr7

E aqui está um trecho:

/**
 * 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);
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top