$ on e $ broadcast em angular

282

Eu tenho um footerController e codeScannerController com diferentes visualizações.

angular.module('myApp').controller('footerController', ["$scope", function($scope) {}]);

angular.module('myApp').controller('codeScannerController', ["$scope", function($scope) {
console.log("start");
$scope.startScanner = function(){...

Quando clico em um <li>no footer.html, devo receber esse evento no codeScannerController.

<li class="button" ng-click="startScanner()">3</li>

Acho que pode ser realizado com $one $broadcast, mas não sei como e não consigo encontrar exemplos em lugar nenhum.

Alice Polansky
fonte

Respostas:

631

Se você deseja $broadcastusar o $rootScope:

$scope.startScanner = function() {

    $rootScope.$broadcast('scanner-started');
}

E para receber, use o $scopedo seu controlador:

$scope.$on('scanner-started', function(event, args) {

    // do what you want to do
});

Se você quiser, pode passar argumentos quando $broadcast:

$rootScope.$broadcast('scanner-started', { any: {} });

E então receba-os:

$scope.$on('scanner-started', function(event, args) {

    var anyThing = args.any;
    // do what you want to do
});

Documentação para isso nos documentos do escopo .

Davin Tryon
fonte
2
Você pode nomear o evento como quiser.
Davin Tryon
5
Verifique se você possui $ scope. $ Apply (); suas mudanças!
Ismail
4
@Ismail Por que ... e onde?
Jaans
7
Existem práticas recomendadas para armazenar essas strings em algum lugar em vez de codificar a mensagem de difusão?
rperryng
8
O @Ismail $scope.$apply()é necessário apenas ao alterar o modelo fora da estrutura angular (como em um setTimeout, um retorno de chamada de diálogo ou retorno de chamada de ajax); em outras palavras, $apply()já é acionado após a conclusão de todo o código .$on().
th3uiguy
97

Primeiro, uma breve descrição de $on(), $broadcast()e$emit() :

  • .$on(name, listener) - Escuta um evento específico por um determinado name
  • .$broadcast(name, args)- Transmitir um evento através $scopede todas as crianças
  • .$emit(name, args)- Emita um evento na $scopehierarquia para todos os pais, incluindo o$rootScope

Com base no seguinte HTML (veja o exemplo completo aqui ):

<div ng-controller="Controller1">
    <button ng-click="broadcast()">Broadcast 1</button>
    <button ng-click="emit()">Emit 1</button>
</div>

<div ng-controller="Controller2">
    <button ng-click="broadcast()">Broadcast 2</button>
    <button ng-click="emit()">Emit 2</button>
    <div ng-controller="Controller3">
        <button ng-click="broadcast()">Broadcast 3</button>
        <button ng-click="emit()">Emit 3</button>
        <br>
        <button ng-click="broadcastRoot()">Broadcast Root</button>
        <button ng-click="emitRoot()">Emit Root</button>
    </div>
</div>

Os eventos disparados percorrerão o $scopesseguinte:

  • Transmissão 1 - Só será visto pelo Controlador 1 $scope
  • Emit 1 - Será visto pelo Controller 1 $scopeentão$rootScope
  • Transmissão 2 - Será visto pelo Controlador 2 e $scopedepois pelo Controlador 3$scope
  • Emit 2 - Será visto pelo Controller 2 $scopeentão$rootScope
  • Transmissão 3 - Só será visto pelo Controlador 3 $scope
  • Emit 3 - Será visto pelo Controller 3 $scope, Controller 2 e $scopedepois$rootScope
  • Raiz de transmissão - será vista por $rootScopee $scopede todos os controladores (1, 2 e 3)
  • Emit Root - Somente será visto por $rootScope

JavaScript para acionar eventos (novamente, você pode ver um exemplo de trabalho aqui ):

app.controller('Controller1', ['$scope', '$rootScope', function($scope, $rootScope){
    $scope.broadcastAndEmit = function(){
        // This will be seen by Controller 1 $scope and all children $scopes 
        $scope.$broadcast('eventX', {data: '$scope.broadcast'});

        // Because this event is fired as an emit (goes up) on the $rootScope,
        // only the $rootScope will see it
        $rootScope.$emit('eventX', {data: '$rootScope.emit'});
    };
    $scope.emit = function(){
        // Controller 1 $scope, and all parent $scopes (including $rootScope) 
        // will see this event
        $scope.$emit('eventX', {data: '$scope.emit'});
    };

    $scope.$on('eventX', function(ev, args){
        console.log('eventX found on Controller1 $scope');
    });
    $rootScope.$on('eventX', function(ev, args){
        console.log('eventX found on $rootScope');
    });
}]);
th3uiguy
fonte
como posso imaginar a hierarquia do meu aplicativo com o exemplo que você deu. Como um controlador pode ser pai ou filho? O que estou tentando dizer é que tenho séries de estados, por exemplo. LoginCtrl -> homeCrl -> notificationCtrl e assim por diante.
HIRA THAKUR
26

Uma coisa que você deve saber é que o prefixo $ se refere a um método angular, os prefixos $$ se referem a métodos angulares que você deve evitar usar.

abaixo está um modelo de exemplo e seus controladores, exploraremos como $ broadcast / $ on pode nos ajudar a alcançar o que queremos.

<div ng-controller="FirstCtrl">
    <input ng-model="name"/> 
    <button ng-click="register()">Register </button>
</div>

<div ng-controller="SecondCtrl">
    Registered Name: <input ng-model="name"/> 
</div>

Os controladores são

app.controller('FirstCtrl', function($scope){
    $scope.register = function(){

    }
});

app.controller('SecondCtrl', function($scope){

});

Minha pergunta para você é como você passa o nome para o segundo controlador quando um usuário clica em registrar? Você pode encontrar várias soluções, mas a que vamos usar está usando $ broadcast e $ on.

$ broadcast vs $ emitem

Qual devemos usar? $ broadcast canalizará para todos os elementos dom filhos e $ emit canalizará a direção oposta a todos os elementos dom ancestrais.

A melhor maneira de evitar decidir entre $ emit ou $ broadcast é canalizar a partir do $ rootScope e usar $ broadcast para todos os seus filhos. O que torna nosso caso muito mais fácil, já que nossos elementos dom são irmãos.

Adicionando $ rootScope e permite que $ broadcast

app.controller('FirstCtrl', function($rootScope, $scope){
    $scope.register = function(){
        $rootScope.$broadcast('BOOM!', $scope.name)
    }
});

Observe que adicionamos $ rootScope e agora estamos usando $ broadcast (broadcastName, argumentos). Para broadcastName, queremos atribuir um nome exclusivo para que possamos pegar esse nome em nosso secondCtrl. Eu escolhi o BOOM! apenas por diversão. Os segundos argumentos 'argumentos' nos permitem passar valores para os ouvintes.

Recebendo nossa transmissão

Em nosso segundo controlador, precisamos configurar o código para ouvir nossa transmissão

app.controller('SecondCtrl', function($scope){
  $scope.$on('BOOM!', function(events, args){
    console.log(args);
    $scope.name = args; //now we've registered!
  })
});

É realmente assim tão simples. Exemplo ao vivo

Outras maneiras de obter resultados semelhantes

Tente evitar o uso desse conjunto de métodos, pois ele não é eficiente nem fácil de manter, mas é uma maneira simples de corrigir problemas que você possa ter.

Geralmente, você pode fazer o mesmo usando um serviço ou simplificando seus controladores. Não discutiremos isso em detalhes, mas pensei em mencioná-lo por completo.

Por fim, lembre-se de que uma transmissão realmente útil para ouvir é '$ destroy' novamente. Você pode ver que $ significa que é um método ou objeto criado pelos códigos do fornecedor. De qualquer forma, $ destroy é transmitido quando um controlador é destruído. Você pode ouvir isso para saber quando seu controlador é removido.

Yang Li
fonte
2
Como aviso, tente não usar muitas transmissões / emissões no seu aplicativo. Eles podem ser extremamente difíceis de gerenciar, especialmente em um aplicativo grande, pois rastrear as raízes desses eventos é uma tarefa muito difícil.
Yang Li
1
//Your broadcast in service

(function () { 
    angular.module('appModule').factory('AppService', function ($rootScope, $timeout) {

    function refreshData() {  
        $timeout(function() {         
            $rootScope.$broadcast('refreshData');
        }, 0, true);      
    }

    return {           
        RefreshData: refreshData
    };
}); }());

//Controller Implementation
 (function () {
    angular.module('appModule').controller('AppController', function ($rootScope, $scope, $timeout, AppService) {            

       //Removes Listeners before adding them 
       //This line will solve the problem for multiple broadcast call                             
       $scope.$$listeners['refreshData'] = [];

       $scope.$on('refreshData', function() {                                                    
          $scope.showData();             
       });

       $scope.onSaveDataComplete = function() { 
         AppService.RefreshData();
       };
    }); }());
Sandy
fonte