¿Cuál es la forma correcta de comunicarse entre los controladores en AngularJS?

¿Cuál es la forma correcta de comunicarse entre los controladores?

Actualmente estoy usando un fudge horrible que implica la window :

 function StockSubgroupCtrl($scope, $http) { $scope.subgroups = []; $scope.handleSubgroupsLoaded = function(data, status) { $scope.subgroups = data; } $scope.fetch = function(prod_grp) { $http.get('/api/stock/groups/' + prod_grp + '/subgroups/').success($scope.handleSubgroupsLoaded); } window.fetchStockSubgroups = $scope.fetch; } function StockGroupCtrl($scope, $http) { ... $scope.select = function(prod_grp) { $scope.selectedGroup = prod_grp; window.fetchStockSubgroups(prod_grp); } } 

Editar : el problema tratado en esta respuesta se ha resuelto en la versión 1.2.7 de angular.js. $broadcast ahora evita burbujear sobre ámbitos no registrados y se ejecuta tan rápido como $ emitir. Los rendimientos de $ $ son idénticos a $ emiten con 1.2.16 angular

Entonces, ahora puedes:

  • use $broadcast desde el $rootScope
  • escucha utilizando $on desde el $scope local que necesita saber sobre el evento

Respuesta original a continuación

Recomiendo encarecidamente no usar $rootScope.$broadcast + $scope.$on $rootScope.$emit sino más bien $rootScope.$emit $rootScope.$on + $rootScope.$on . El primero puede causar serios problemas de rendimiento como lo plantea @numan. Eso es porque el evento burbujeará a través de todos los ámbitos.

Sin embargo, este último (utilizando $rootScope.$emit + $rootScope.$on ) no sufre de esto y, por lo tanto, puede utilizarse como un canal de comunicación rápido.

De la documentación angular de $emit :

Despacha un nombre de evento hacia arriba a través de la jerarquía de scope notificando a los registrados

Como no existe un scope superior a $rootScope , no se produce burbujeo. Es totalmente seguro usar $rootScope.$emit() / $rootScope.$on() como EventBus.

Sin embargo, hay una gotcha cuando se usa desde Controladores. Si se vincula directamente a $rootScope.$on() desde un controlador, tendrá que limpiar el enlace usted mismo cuando se destruya su $scope local. Esto se debe a que los controladores (a diferencia de los servicios) pueden crearse instancias varias veces durante la vida útil de una aplicación, lo que daría como resultado enlaces que terminarían por crear memory leaks en todo el lugar 🙂

Para cancelar el registro, simplemente escuche el evento $destroy $scope y luego llame a la función devuelta por $rootScope.$on .

 angular .module('MyApp') .controller('MyController', ['$scope', '$rootScope', function MyController($scope, $rootScope) { var unbind = $rootScope.$on('someComponent.someCrazyEvent', function(){ console.log('foo'); }); $scope.$on('$destroy', unbind); } ]); 

Yo diría que eso no es realmente algo específico, ya que también se aplica a otras implementaciones de EventBus, que debe limpiar los recursos.

Sin embargo, puede hacer su vida más fácil para esos casos. Por ejemplo, podría parchear $rootScope y darle un $onRootScope que se suscriba a eventos emitidos en $rootScope pero también limpia directamente el controlador cuando se destruye el $scope local.

La forma más limpia de parchear el $rootScope para proporcionar dicho método $onRootScope sería a través de un decorador (un bloque de ejecución probablemente también lo haga bien, pero pssst, no se lo digas a nadie)

Para asegurarse de que la propiedad $onRootScope no aparece inesperada al enumerar $scope , utilizamos Object.defineProperty() y establecemos enumerable en false . Tenga en cuenta que es posible que necesite una cuña ES5.

 angular .module('MyApp') .config(['$provide', function($provide){ $provide.decorator('$rootScope', ['$delegate', function($delegate){ Object.defineProperty($delegate.constructor.prototype, '$onRootScope', { value: function(name, listener){ var unsubscribe = $delegate.$on(name, listener); this.$on('$destroy', unsubscribe); return unsubscribe; }, enumerable: false }); return $delegate; }]); }]); 

Con este método en su lugar, el código del controlador de arriba se puede simplificar a:

 angular .module('MyApp') .controller('MyController', ['$scope', function MyController($scope) { $scope.$onRootScope('someComponent.someCrazyEvent', function(){ console.log('foo'); }); } ]); 

Entonces, como resultado final de todo esto, le recomiendo usar $rootScope.$emit + $scope.$onRootScope .

Por cierto, estoy tratando de convencer al equipo angular para abordar el problema dentro del núcleo angular. Hay una discusión pasando aquí: https://github.com/angular/angular.js/issues/4574

Aquí hay una jsperf que muestra cuánto de un impacto de $broadcast trae a la mesa en un escenario decente con solo 100 $scope ‘s.

http://jsperf.com/rootscope-emit-vs-rootscope-broadcast

resultados jsperf

La respuesta principal aquí fue una solución de un problema angular que ya no existe (al menos en las versiones> 1.2.16 y “probablemente antes”) como ha mencionado @zumalifeguard. Pero me quedé leyendo todas estas respuestas sin una solución real.

Me parece que la respuesta ahora debería ser

  • use $broadcast desde el $rootScope
  • escucha utilizando $on desde el $scope local que necesita saber sobre el evento

Entonces para publicar

 // EXAMPLE PUBLISHER angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope', function ($rootScope, $scope) { $rootScope.$broadcast('topic', 'message'); }]); 

Y suscribirse

 // EXAMPLE SUBSCRIBER angular.module('test').controller('ctrlSubscribe', ['$scope', function ($scope) { $scope.$on('topic', function (event, arg) { $scope.receiver = 'got your ' + arg; }); }]); 

Plunkers

  • Sintaxis $ scope regular (como se ve arriba)
  • nuevo Controller As syntax

Si registra el oyente en $scope local, $destroy se $destroy automáticamente cuando se elimine el controlador asociado.

Usando $ rootScope. $ Broadcast y $ scope. $ On para una comunicación de PubSub.

Además, consulte esta publicación: AngularJS: comunicación entre controladores

Como defineProperty tiene un problema de compatibilidad con el navegador, creo que podemos pensar en usar un servicio.

 angular.module('myservice', [], function($provide) { $provide.factory('msgBus', ['$rootScope', function($rootScope) { var msgBus = {}; msgBus.emitMsg = function(msg) { $rootScope.$emit(msg); }; msgBus.onMsg = function(msg, scope, func) { var unbind = $rootScope.$on(msg, func); scope.$on('$destroy', unbind); }; return msgBus; }]); }); 

y usarlo en el controlador de esta manera:

  • controlador 1

     function($scope, msgBus) { $scope.sendmsg = function() { msgBus.emitMsg('somemsg') } } 
  • controlador 2

     function($scope, msgBus) { msgBus.onMsg('somemsg', $scope, function() { // your logic }); } 

GridLinked publicó una solución PubSub que parece estar diseñada bastante bien. El servicio se puede encontrar, aquí .

También un diagtwig de su servicio:

Servicio de mensajería

En realidad, usar emitir y emitir es ineficiente porque el evento sube y baja de la jerarquía de scope, que puede degradarse fácilmente en una botella de rendimiento para una aplicación compleja.

Sugeriría usar un servicio. Así es como lo implementé recientemente en uno de mis proyectos: https://gist.github.com/3384419 .

Idea básica: registrar un bus pubsub / event como servicio. Luego, inserte ese eventbus donde necesite suscribirse o publicar eventos / temas.

Al usar los métodos get y set dentro de un servicio, puede pasar mensajes entre los controladores con mucha facilidad.

 var myApp = angular.module("myApp",[]); myApp.factory('myFactoryService',function(){ var data=""; return{ setData:function(str){ data = str; }, getData:function(){ return data; } } }) myApp.controller('FirstController',function($scope,myFactoryService){ myFactoryService.setData("Im am set in first controller"); }); myApp.controller('SecondController',function($scope,myFactoryService){ $scope.rslt = myFactoryService.getData(); }); 

en HTML HTML puedes verificar de esta manera

 
{{rslt}}

En cuanto al código original, parece que desea compartir datos entre ámbitos. Para compartir Datos o Estado entre $ scope, los documentos sugieren usar un servicio:

  • Para ejecutar código sin estado o con estado compartido entre controladores: use servicios angulares en su lugar.
  • Para crear instancias o administrar el ciclo de vida de otros componentes (por ejemplo, para crear instancias de servicio).

Ref: Angular Docs enlace aquí

De hecho, comencé a usar Postal.js como un bus de mensajes entre los controladores.

Hay muchos beneficios como un bus de mensajes como enlaces de estilo AMQP, la forma en que postal puede integrar w / iFrames y sockets web, y muchas cosas más.

Usé un decorador para configurar Postal en $scope.$bus

 angular.module('MyApp') .config(function ($provide) { $provide.decorator('$rootScope', ['$delegate', function ($delegate) { Object.defineProperty($delegate.constructor.prototype, '$bus', { get: function() { var self = this; return { subscribe: function() { var sub = postal.subscribe.apply(postal, arguments); self.$on('$destroy', function() { sub.unsubscribe(); }); }, channel: postal.channel, publish: postal.publish }; }, enumerable: false }); return $delegate; }]); }); 

Aquí hay un enlace a una publicación de blog sobre el tema …
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/

Así es como lo hago con Factory / Services y la dependency injection simple (DI) .

 myApp = angular.module('myApp', []) # PeopleService holds the "data". angular.module('myApp').factory 'PeopleService', ()-> [ {name: "Jack"} ] # Controller where PeopleService is injected angular.module('myApp').controller 'PersonFormCtrl', ['$scope','PeopleService', ($scope, PeopleService)-> $scope.people = PeopleService $scope.person = {} $scope.add = (person)-> # Simply push some data to service PeopleService.push angular.copy(person) ] # ... and again consume it in another controller somewhere... angular.module('myApp').controller 'PeopleListCtrl', ['$scope','PeopleService', ($scope, PeopleService)-> $scope.people = PeopleService ] 

Me gustó la forma en que se usó $rootscope.emit para lograr la intercomunicación. Sugiero la solución limpia y eficaz para el rendimiento sin contaminar el espacio global.

 module.factory("eventBus",function (){ var obj = {}; obj.handlers = {}; obj.registerEvent = function (eventName,handler){ if(typeof this.handlers[eventName] == 'undefined'){ this.handlers[eventName] = []; } this.handlers[eventName].push(handler); } obj.fireEvent = function (eventName,objData){ if(this.handlers[eventName]){ for(var i=0;i 

Aquí está la manera rápida y sucia.

 // Add $injector as a parameter for your controller function myAngularController($scope,$injector){ $scope.sendorders = function(){ // now you can use $injector to get the // handle of $rootScope and broadcast to all $injector.get('$rootScope').$broadcast('sinkallships'); }; } 

Aquí hay una función de ejemplo para agregar dentro de cualquiera de los controladores hermanos:

 $scope.$on('sinkallships', function() { alert('Sink that ship!'); }); 

y por supuesto aquí está tu HTML:

  

Puede acceder a esta función de saludo en cualquier parte del módulo

Controlador uno

  $scope.save = function() { $scope.hello(); } 

segundo controlador

  $rootScope.hello = function() { console.log('hello'); } 

Más información aquí

Crearé un servicio y usaré la notificación.

  1. Crear un método en el Servicio de notificaciones
  2. Cree un método genérico para transmitir notificaciones en el Servicio de notificaciones.
  3. Desde el controlador fuente, llama a notificationService.Method. También paso el objeto correspondiente para persistir si es necesario.
  4. Dentro del método, persisto los datos en el servicio de notificación y llamo al método de notificación genérico.
  5. En el controlador de destino, escucho ($ scope.on) para el evento de difusión y accedo a los datos del Servicio de Notificación.

Como en cualquier momento el Servicio de notificación es único, debería poder proporcionar datos persistentes.

Espero que esto ayude

Puede usar el servicio de comstackción AngularJS $rootScope e inyectar este servicio en sus dos controladores. Luego puede escuchar los eventos que se disparan en el objeto $ rootScope.

$ rootScope proporciona dos despachadores de eventos llamados $emit and $broadcast que son responsables de despachar eventos (pueden ser eventos personalizados) y usar $rootScope.$on función para agregar el detector de eventos.

Debería usar el Servicio, porque $rootscope es acceso desde toda la Aplicación, y aumenta la carga, o usted usa los rootparams si sus datos no son más.

 function mySrvc() { var callback = function() { } return { onSaveClick: function(fn) { callback = fn; }, fireSaveClick: function(data) { callback(data); } } } function controllerA($scope, mySrvc) { mySrvc.onSaveClick(function(data) { console.log(data) }) } function controllerB($scope, mySrvc) { mySrvc.fireSaveClick(data); } 

Puedes hacerlo usando eventos angulares que son $ emitir y $ emitir. Según nuestro conocimiento, esta es la mejor, eficiente y efectiva manera.

Primero llamamos a una función desde un controlador.

 var myApp = angular.module('sample', []); myApp.controller('firstCtrl', function($scope) { $scope.sum = function() { $scope.$emit('sumTwoNumber', [1, 2]); }; }); myApp.controller('secondCtrl', function($scope) { $scope.$on('sumTwoNumber', function(e, data) { var sum = 0; for (var a = 0; a < data.length; a++) { sum = sum + data[a]; } console.log('event working', sum); }); }); 

También puede usar $ rootScope en lugar de $ scope. Use su controlador en consecuencia.

Inicia angular 1.5 y su enfoque de desarrollo basado en componentes. La forma recomendada para que los componentes interactúen es a través del uso de la propiedad ‘requerir’ y mediante enlaces de propiedad (entrada / salida).

Un componente requeriría otro componente (por ejemplo, el componente raíz) y obtendría una referencia a su controlador:

 angular.module('app').component('book', { bindings: {}, require: {api: '^app'}, template: 'Product page of the book: ES6 - The Essentials', controller: controller }); 

A continuación, puede usar los métodos del componente raíz en su componente secundario:

 $ctrl.api.addWatchedBook('ES6 - The Essentials'); 

Esta es la función del controlador del componente raíz:

 function addWatchedBook(bookName){ booksWatched.push(bookName); } 

Aquí hay una visión general arquitectónica completa: Component Communications