$emit ou $broadcast eventos do serviço e ouça-os em um controlador (ou vários)
-
21-12-2019 - |
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();
}]);
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);
}