¿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.
Entonces, ahora puedes:
$broadcast
desde el $rootScope
$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
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
$broadcast
desde el $rootScope
$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
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:
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:
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.
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