scope - que - servicios en angular js
¿Cuál es la forma correcta de comunicarse entre los controladores en AngularJS? (19)
¿Cuál es la forma correcta de comunicarse entre los controladores?
Actualmente estoy usando un horrible fudge con 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);
}
}
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á su HTML:
<button ngclick="sendorders()">Sink Enemy Ships</button>
Así es como lo hago con Factory / Services y inyección de dependencia 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
]
Con los métodos de obtención y configuración dentro de un servicio, puede pasar mensajes entre los controladores de manera muy sencilla.
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 como este
<div ng-controller=''FirstController''>
</div>
<div ng-controller=''SecondController''>
{{rslt}}
</div>
Crearé una notificación de servicio y uso.
- Crear un método en el servicio de notificaciones.
- Cree un método genérico para transmitir notificaciones en el Servicio de Notificación.
- Desde el controlador de origen llame al notificationService.Method. También paso el objeto correspondiente para persistir si es necesario.
- Dentro del método, conservo los datos en el servicio de notificación y llamo al método de notificación genérico.
- En el controlador de destino escucho ($ scope.on) el evento de transmisión y accedo a los datos del Servicio de Notificación.
Como en cualquier punto, el Servicio de Notificación es único, debe poder proporcionar datos persistentes en todo.
Espero que esto ayude
Dado que 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 }); }
De hecho, comencé a usar Postal.js como un bus de mensajes entre los controladores.
Hay muchos beneficios para él como un bus de mensajes como los enlaces de estilo AMQP, la forma en que postal puede integrarse con iFrames y sockets web, y muchas más cosas.
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 entrada de blog sobre el tema ...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/
Debe usar el Servicio, ya que $rootscope
es el acceso desde la aplicación completa, y aumenta la carga, o puede usar los rootparams si sus datos no son más.
En realidad, el uso de emitir y transmitir es ineficiente porque el evento aumenta y disminuye la jerarquía del alcance, lo que puede degradarse fácilmente en el rendimiento de una aplicación compleja.
Sugeriría utilizar un servicio. Aquí es cómo lo implementé recientemente en uno de mis proyectos: https://gist.github.com/3384419 .
Idea básica: registrar un pubsub / evento bus como un servicio. Luego inyecte ese bus de eventos donde sea que necesite suscribirse o publicar eventos / temas.
Iniciando angular 1.5 y su enfoque de desarrollo basado en componentes. La forma recomendada para que los componentes interactúen es mediante el uso de la propiedad ''require'' y mediante los enlaces de propiedad (entrada / salida).
Un componente requeriría otro componente (por ejemplo, el componente raíz) y obtener 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
});
Luego puede usar los métodos del componente raíz en su componente hijo:
$ctrl.api.addWatchedBook(''ES6 - The Essentials'');
Esta es la función del controlador de componente raíz:
function addWatchedBook(bookName){
booksWatched.push(bookName);
}
Aquí está una descripción arquitectónica completa: Comunicaciones del componente
La respuesta principal aquí fue una solución a un problema Angular que ya no existe (al menos en las versiones> 1.2.16 y "probablemente antes") como lo mencionó @zumalifeguard. Pero me quedo leyendo todas estas respuestas sin una solución real.
Me parece que la respuesta ahora debería ser
- usar
$broadcast
desde$rootScope
- escuchar usando
$on
desde el$scope
local$scope
que necesita saber sobre el evento
Así que 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 regular de $ alcance (como se ve arriba)
- Nuevo
Controller As
sintaxis
Si registra el oyente en el $scope
local, $scope
se destruirá automáticamente cuando se elimine el controlador asociado.
Me gustó la forma en que se utilizó $rootscope.emit
para lograr la intercomunicación. Sugiero la solución limpia y eficaz en 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<this.handlers[eventName].length;i++){
this.handlers[eventName][i](objData);
}
}
}
return obj;
})
//Usage:
//In controller 1 write:
eventBus.registerEvent(''fakeEvent'',handler)
function handler(data){
alert(data);
}
//In controller 2 write:
eventBus.fireEvent(''fakeEvent'',''fakeData'');
Puede usar el servicio $rootScope
e inyectar este servicio en sus dos controladores. Luego puede escuchar los eventos que se activan en el objeto $ rootScope.
$ rootScope proporciona dos despachadores de eventos llamados $emit and $broadcast
que son responsables de enviar eventos (pueden ser eventos personalizados) y usan $rootScope.$on
función para agregar un detector de eventos.
Puedes 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'');
}
Puedes hacerlo usando eventos angulares que son $ emit y $ broadcast. Según nuestro conocimiento, esta es la mejor manera, eficiente y eficaz.
Primero llamamos 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. Utilice su controlador en consecuencia.
Respecto al código original: parece que desea compartir datos entre ámbitos. Para compartir datos o estados entre $ alcance, los documentos sugieren usar un servicio:
- Para ejecutar código sin estado o con estado compartido entre los controladores: use servicios angulares en su lugar.
- Para crear una instancia o administrar el ciclo de vida de otros componentes (por ejemplo, para crear instancias de servicio).
Utilizando $rootScope.$broadcast y $ scope. $rootScope.$broadcast para una comunicación de PubSub.
También, vea este post: AngularJS - Comunicando entre controladores
GridLinked publicó una solución de PubSub que parece estar diseñada bastante bien. El servicio se puede encontrar, here .
También un diagrama de su servicio:
Edición : el problema tratado en esta respuesta se resolvió en la versión 1.2.7 de angular.js. $broadcast
ahora evita las burbujas en los ámbitos no registrados y se ejecuta tan rápido como $ emit.
Entonces, ahora puedes:
- usar
$broadcast
desde$rootScope
- escuchar usando
$on
desde el$scope
local$scope
que necesita saber sobre el evento
Respuesta original abajo
Recomiendo no usar $rootScope.$broadcast
+ $scope.$on
$rootScope.$emit
sino más bien $rootScope.$emit
$rootScope.$on
+ $rootScope.$on
. Lo primero puede causar serios problemas de rendimiento como lo plantea @numan. Eso es porque el evento se derrumbará a través de todos los ámbitos.
Sin embargo, este último (usando $rootScope.$emit
+ $rootScope.$on
) no sufre de esto y, por lo tanto, se puede usar como un canal de comunicación rápida.
De la documentación angular de $emit
:
Envía un nombre de evento hacia arriba a través de la jerarquía de alcance notificando a los registrados
Dado que no hay un alcance por encima de $rootScope
, no hay ningún $rootScope
. Es totalmente seguro usar $rootScope.$emit()
/ $rootScope.$on()
como EventBus.
Sin embargo, hay un gotcha cuando se usa desde los controladores. Si vincula directamente a $rootScope.$on()
desde un controlador, deberá limpiar el enlace usted mismo cuando se destruya su $scope
local. Esto se debe a que los controladores (a diferencia de los servicios) se pueden crear instancias varias veces durante la vida útil de una aplicación, lo que daría como resultado una combinación de enlaces que eventualmente crearía pérdidas de memoria en todo el lugar :)
Para cancelar el registro, simplemente escuche el evento $destroy
$scope
y luego llame a la función que devolvió $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);
}
]);
Diría que eso no es realmente una cosa específica angular, ya que también se aplica a otras implementaciones de EventBus, que tiene que limpiar los recursos.
Sin embargo, puede hacer su vida más fácil para esos casos. Por ejemplo, podría $rootScope
parche $rootScope
y darle un $onRootScope
que se suscriba a los 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 hará bien, pero pssst, no se lo digas a nadie)
Para asegurarnos de que la propiedad $onRootScope
no se muestre inesperada al enumerar sobre $scope
, usamos Object.defineProperty()
y configuramos enumerable
en false
. Tenga en cuenta que es posible que necesite un calzo 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 para:
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 que use $rootScope.$emit
+ $scope.$onRootScope
.
Por cierto, estoy tratando de convencer al equipo angular para que aborde el problema dentro del núcleo angular. Aquí hay una discusión: https://github.com/angular/angular.js/issues/4574
Aquí hay un jsperf que muestra cuánto del impacto de $broadcast
llega a la mesa en un escenario decente con solo 100 $scope
.
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);
}