Quelle est la bonne façon de communiquer entre contrôleurs?
J'utilise actuellement un fudge horrible impliquant 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);
}
}
Edit: Le problème traité dans cette réponse a été résolu dans angular.js version 1.2.7 . $broadcast
évite désormais les bulles sur les portées non enregistrées et s'exécute aussi rapidement que $ emit .
Alors, maintenant vous pouvez:
$broadcast
à partir du $rootScope
$on
à partir du $scope
local qui a besoin de connaître l'événement_ {Réponse originale ci-dessous}
Je conseille vivement de ne pas utiliser $rootScope.$broadcast
+ $scope.$on
mais plutôt $rootScope.$emit
+ $rootScope.$on
. Le premier peut causer de graves problèmes de performances tels que soulevés par @numan. Cela est dû au fait que l'événement va se propager dans tous champs.
Cependant, ce dernier (en utilisant $rootScope.$emit
+ $rootScope.$on
) en souffre pas _ et peut donc être utilisé comme canal de communication rapide!
De la documentation angulaire de $emit
:
Répartit un nom d’événement vers le haut dans la hiérarchie de la portée en notifiant le
Puisqu'il n'y a pas de portée au-dessus de $rootScope
, il n'y a pas de bouillonnement. Il est totalement sûr d'utiliser $rootScope.$emit()
/$rootScope.$on()
en tant que bus événementiel.
Cependant, il y a un casse-tête lorsque vous l'utilisez depuis les contrôleurs. Si vous vous liez directement à $rootScope.$on()
depuis un contrôleur, vous devrez nettoyer la liaison vous-même lorsque votre $scope
local sera détruit. Cela est dû au fait que les contrôleurs (contrairement aux services) peuvent être instanciés plusieurs fois au cours de la vie d’une application, ce qui résulterait en des liaisons résumant éventuellement la création de fuites de mémoire partout :)
Pour vous désinscrire, écoutez simplement l'événement $scope
de votre $destroy
, puis appelez la fonction renvoyée par $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);
}
]);
Je dirais que ce n’est pas vraiment un problème angulaire, comme cela s’applique également à d’autres implémentations d’EventBus, qu’il faut nettoyer les ressources.
Cependant, vous pouvez vous simplifier la vie. Par exemple, vous pouvez utiliser le patch $rootScope
et lui donner un $onRootScope
qui s'abonne aux événements émis sur le $rootScope
mais nettoie également le gestionnaire lorsque le $scope
local est détruit.
Le moyen le plus propre de corriger le $rootScope
pour fournir une telle méthode $onRootScope
serait de passer par un décorateur (un bloc d'exécution le fera probablement aussi bien, mais pssst, ne le dites à personne)
Pour vous assurer que la propriété $onRootScope
ne s'affiche pas de manière inattendue lors de l'énumération de $scope
, nous utilisons Object.defineProperty()
et définissons enumerable
à false
. N'oubliez pas que vous pourriez avoir besoin d'une cale 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;
}]);
}]);
Avec cette méthode en place, le code de contrôleur ci-dessus peut être simplifié pour:
angular
.module('MyApp')
.controller('MyController', ['$scope', function MyController($scope) {
$scope.$onRootScope('someComponent.someCrazyEvent', function(){
console.log('foo');
});
}
]);
Donc, comme résultat final de tout cela, je vous conseille vivement d’utiliser $rootScope.$emit
+ $scope.$onRootScope
.
Au fait, j'essaie de convaincre l'équipe angulaire de résoudre le problème dans le noyau angulaire. Une discussion est en cours ici: https://github.com/angular/angular.js/issues/4574
Voici un jsperf qui montre à quel point $broadcast
a un impact sur la performance dans un scénario décent avec seulement 100 $scope
's.
La réponse de top était un problème autour d'un problème angulaire qui n'existe plus (du moins dans les versions> 1.2.16 et "probablement antérieure"), comme l'a mentionné @zumalifeguard. Mais il me reste à lire toutes ces réponses sans solution réelle.
Il me semble que la réponse devrait maintenant être
$broadcast
à partir du $rootScope
$on
à partir du $scope
local qui doit connaître l'événementDonc pour publier
// EXAMPLE PUBLISHER
angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope',
function ($rootScope, $scope) {
$rootScope.$broadcast('topic', 'message');
}]);
Et abonnez-vous
// EXAMPLE SUBSCRIBER
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {
$scope.$on('topic', function (event, arg) {
$scope.receiver = 'got your ' + arg;
});
}]);
Plunkers
Controller As
Si vous enregistrez l'écouteur sur le $scope
local, il sera détruit automatiquement par $destroy
lui-même lorsque le contrôleur associé sera supprimé.
Utilisation de $ rootScope. $ Broadcast et $ scope. $ On pour une communication PubSub.
Voir aussi cet article: AngularJS - Communication entre contrôleurs
Etant donné que defineProperty a un problème de compatibilité de navigateur, je pense que nous pouvons penser à utiliser un service.
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;
}]);
});
et l'utiliser dans le contrôleur comme ceci:
contrôleur 1
function($scope, msgBus) {
$scope.sendmsg = function() {
msgBus.emitMsg('somemsg')
}
}
contrôleur 2
function($scope, msgBus) {
msgBus.onMsg('somemsg', $scope, function() {
// your logic
});
}
GridLinked posted a PubSub solution qui semble assez bien conçue. Le service peut être trouvé, ici .
Également un schéma de leur service:
En réalité, l'utilisation d'émettre et de diffuser est inefficace, car les bulles montent et descendent dans la hiérarchie de l'étendue, ce qui peut facilement dégrader en performances pour une application complexe.
Je suggère d'utiliser un service. Voici comment je l'ai récemment implémenté dans l'un de mes projets - https://Gist.github.com/3384419 .
Idée de base - enregistrez un bus de pub/événement en tant que service. Ensuite, injectez cet événement où vous avez besoin de souscrire ou de publier des événements/sujets.
En utilisant les méthodes get et set au sein d'un service, vous pouvez très facilement transmettre des messages entre contrôleurs.
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, vous pouvez vérifier comme ça
<div ng-controller='FirstController'>
</div>
<div ng-controller='SecondController'>
{{rslt}}
</div>
En ce qui concerne le code d'origine, il semble que vous souhaitiez partager des données entre des portées. Pour partager des données ou des états entre $ scope, les docs suggèrent d'utiliser un service:
En fait, j'ai commencé à utiliser Postal.js en tant que bus de messages entre contrôleurs.
Il comporte de nombreux avantages en tant que bus de messages, tels que des liaisons de style AMQP, la manière dont la poste peut intégrer des cadres iFrames et Web, et bien d’autres choses encore.
J'ai utilisé un décorateur pour installer Postal sur $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;
}]);
});
Voici un lien vers un article de blog sur le sujet ...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/
Voici comment je le fais avec Factory/Services et simple injection de dépendance (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
]
J'ai aimé la façon dont $rootscope.emit
a été utilisé pour réaliser l'intercommunication. Je suggère la solution propre et efficace sans polluer l’espace 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');
Voici le moyen rapide et sale.
// 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');
};
}
Voici un exemple de fonction à ajouter dans l'un des contrôleurs frères:
$scope.$on('sinkallships', function() {
alert('Sink that ship!');
});
et bien sûr voici votre code HTML:
<button ngclick="sendorders()">Sink Enemy Ships</button>
Vous pouvez accéder à cette fonction hello n'importe où dans le module
Contrôleur un
$scope.save = function() {
$scope.hello();
}
deuxième contrôleur
$rootScope.hello = function() {
console.log('hello');
}
Démarrage de angular 1.5 et de son axe de développement basé sur les composants. La méthode recommandée pour que les composants interagissent consiste à utiliser la propriété 'require' et à travers les liaisons de propriété (entrée/sortie).
Un composant nécessiterait un autre composant (par exemple le composant racine) et obtiendrait une référence à son contrôleur:
angular.module('app').component('book', {
bindings: {},
require: {api: '^app'},
template: 'Product page of the book: ES6 - The Essentials',
controller: controller
});
Vous pouvez ensuite utiliser les méthodes du composant racine dans votre composant enfant:
$ctrl.api.addWatchedBook('ES6 - The Essentials');
Voici la fonction du contrôleur de composant racine:
function addWatchedBook(bookName){
booksWatched.Push(bookName);
}
Voici un aperçu complet de l'architecture: Component Communications
Vous pouvez utiliser le service intégré AngularJS $rootScope
et injecter ce service dans vos deux contrôleurs . Vous pouvez ensuite écouter les événements déclenchés sur l'objet $ rootScope.
$ rootScope fournit deux répartiteurs d’événements appelés $emit and $broadcast
qui sont responsables de la distribution des événements (peuvent être des événements personnalisés) et utilisent la fonction $rootScope.$on
pour ajouter un écouteur d’événements.
Vous pouvez le faire en utilisant des événements angulaires tels que $ emit et $ broadcast. À notre connaissance, il s’agit du moyen le plus efficace et le plus efficace.
Nous appelons d’abord une fonction d’un contrôleur.
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);
});
});
Vous pouvez également utiliser $ rootScope à la place de $ scope. Utilisez votre contrôleur en conséquence.
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);
}
Vous devez utiliser le service, car $rootscope
correspond à l’accès de l’ensemble de l’application et augmente la charge, ou vous utilisez les rootparams si vos données ne sont pas plus nombreuses.
Je vais créer un service et utiliser une notification.
Comme à tout moment, Notification Service est un singleton, il devrait pouvoir fournir des données persistantes.
J'espère que cela t'aides