Quelle est la bonne façon de communiquer entre les contrôleurs à AngularJS?
Quelle est la bonne façon de communiquer entre les contrôleurs?
j'utilise actuellement un horrible caramel 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);
}
}
19 réponses
Edit : la question abordée dans cette réponse a été résolue en angle.js version 1.2.7 . $broadcast
évite maintenant les bulles sur les scopes non enregistrés et fonctionne aussi vite que $emit.
donc, maintenant vous pouvez:
- utiliser
$broadcast
du$rootScope
- écouter à l'aide de
$on
du local$scope
qui a besoin de savoir à propos de l'événement
Réponse Originale Ci-Dessous
je recommande fortement de ne pas utiliser $rootScope.$broadcast
+ $scope.$on
mais plutôt $rootScope.$emit
+ $rootScope.$on
. Les premiers peuvent causer de graves problèmes de performance comme le souligne @numan. C'est parce que l'événement va descendre à travers tous portée.
cependant, ce dernier (en utilisant $rootScope.$emit
+ $rootScope.$on
) ne pas souffrent de cela et peuvent donc être utilisés comme un canal de communication rapide!
de la documentation angulaire de $emit
:
Distribue un événement nom vers le haut à travers le champ d'application de la hiérarchie de la notification enregistrée
Puisqu'il n'y a pas de champ d'application ci-dessus $rootScope
, il n'y a pas de bulles. Il est totalement sûr d'utiliser $rootScope.$emit()
/ $rootScope.$on()
comme un EventBus.
cependant, il y a un gotcha lors de l'utilisation de L'intérieur des contrôleurs. Si vous vous liez directement à $rootScope.$on()
depuis un contrôleur, vous devrez nettoyer vous-même la liaison 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 durée de vie d'une application. ce qui aboutirait à des reliures résumant éventuellement la création de fuites de mémoire partout:)
pour vous désinscrire, il vous suffit d'écouter votre événement $scope
's $destroy
et ensuite d'appeler la fonction qui a été retourné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, ce n'est pas vraiment une chose angulaire spécifique comme il s'applique à d'autres implémentations EventBus aussi, que vous devez nettoyer les ressources.
cependant, vous pouvez rendre votre vie plus facile pour ces cas. Par exemple, vous pouvez patcher $rootScope
et lui donner un $onRootScope
qui souscrit aux événements émis sur le $rootScope
mais nettoie aussi directement le handler lorsque le local $scope
est détruit.
le moyen le plus propre de patcher le singe $rootScope
pour fournir une telle méthode $onRootScope
serait par un décorateur (un bloc de course fera probablement très bien ainsi, mais pssst, ne le dis à personne)
pour s'assurer que la propriété $onRootScope
n'apparaît pas inattendu en énumérant plus de $scope
nous utilisons Object.defineProperty()
et réglons enumerable
à false
. Gardez à l'esprit que vous pourriez avoir besoin D'un shim 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 du contrôleur d'en haut peut être simplifié par:
angular
.module('MyApp')
.controller('MyController', ['$scope', function MyController($scope) {
$scope.$onRootScope('someComponent.someCrazyEvent', function(){
console.log('foo');
});
}
]);
comme résultat final de tout cela je vous conseille vivement d'utiliser $rootScope.$emit
+ $scope.$onRootScope
.
Btw, j'essaie de convaincre l'équipe angulaire de résoudre le problème dans le noyau angulaire. Il y a une discussion ici: https://github.com/angular/angular.js/issues/4574
Voici un jsperf qui montre combien la perf impact $broadcast
apporte à la table dans un bon scénario avec seulement 100 $scope
.
http://jsperf.com/rootscope-emit-vs-rootscope-broadcast
le top answer voici un travail autour d'un problème angulaire qui n'existe plus (au moins dans les versions >1.2.16 et "probablement plus tôt") comme @zumalifeguard l'a mentionné. Mais je suis en train de lire toutes ces réponses sans une VRAIE solution.
Il me semble que la réponse devrait être
- utiliser
$broadcast
du$rootScope
- écouter à l'aide de
$on
du local$scope
qui a besoin de savoir à propos de l'événement
Afin de publier
// EXAMPLE PUBLISHER
angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope',
function ($rootScope, $scope) {
$rootScope.$broadcast('topic', 'message');
}]);
et s'abonner
// EXAMPLE SUBSCRIBER
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {
$scope.$on('topic', function (event, arg) {
$scope.receiver = 'got your ' + arg;
});
}]);
Plunkers
- syntaxe régulière $scope (comme vous le voyez ci-dessus)
- nouveau
Controller As
syntaxe
si vous enregistrez l'auditeur sur le local $scope
, il sera détruit automatiquement par $destroy
lui-même lorsque le contrôleur associé est supprimé.
utilisant $rootScope.$ diffuser et $ scope.$ on pour une communication PubSub.
Aussi, voir ce post: AngularJS – la Communication Entre les Contrôleurs
depuis defineProperty a problème de compatibilité avec le 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 posté un PubSub solution qui semble être conçu assez bien. Le service peut être trouvé, ici .
aussi un schéma de leur service:
réellement en utilisant emit et broadcast est inefficace parce que l'événement bulles de haut en bas de la hiérarchie de portée qui peut facilement se dégrader en performance bottlement pour une application complexe.
je suggère d'utiliser un service. Voici comment je l'ai récemment mis en œuvre dans un de mes projets - https://gist.github.com/3384419 .
idée de base-enregistrer un bus pubsub / événement comme un service. Puis injectez cet eventbus où jamais vous avez besoin de vous abonner ou de publier des événements et sujets.
en utilisant des méthodes get et set dans un service, vous pouvez passer des messages entre les contrôleurs très facilement.
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 ceci
<div ng-controller='FirstController'>
</div>
<div ng-controller='SecondController'>
{{rslt}}
</div>
en ce qui concerne le code original - il semble que vous voulez partager des données entre les portées. Pour partager des données ou indiquer entre $scope les docs suggèrent d'utiliser un service:
- pour exécuter le code d'apatridie ou d'état partagé entre les contrôleurs - utiliser services angulaires à la place.
- Pour instancier ou de gérer le cycle de vie de d'autres composants (par exemple, pour créer des instances de service).
j'ai commencé à utiliser la poste.js comme un bus de message entre les contrôleurs.
il y a beaucoup d'avantages à cela en tant que bus de messages tels que les fixations de style AMQP, la façon dont postal peut intégrer w/ iFrames et sockets web, et beaucoup plus de choses.
j'ai utilisé un décorateur pour faire installer la poste 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 billet de blog sur le sujet...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js /
C'est comme ça que je le fais avec usine / 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 de performance 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 chemin 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 HTML:
<button ngclick="sendorders()">Sink Enemy Ships</button>
vous pouvez accéder à cette fonction hello n'importe où dans le module
Contrôleur une
$scope.save = function() {
$scope.hello();
}
deuxième contrôleur
$rootScope.hello = function() {
console.log('hello');
}
je vais créer un service et utiliser la notification.
- créer une méthode dans le service de Notification
- Créer une méthode générique pour la diffusion de notification dans le Service de Notification.
- du contrôleur source appeler le service de notification.Méthode. Je passe également l'objet correspondant à persister si nécessaire.
- dans la méthode, je persiste données dans le service de notification et appel Générique méthode de notification.
- dans le contrôleur de destination j'écoute ($scope.on) pour l'événement de diffusion et accéder aux données du Service de Notification.
comme à tout moment le service de Notification est unique, il devrait être en mesure de fournir des données persistées.
Espérons que cette aide
vous pouvez utiliser AngularJS build-in service $rootScope
et injecter ce service dans vos deux contrôleurs.
Vous pouvez alors écouter les événements qui sont lancés sur l'objet $rootScope.
$rootScope fournit deux event dispatcher appelé $emit and $broadcast
qui sont responsables de l'expédition des événements(peut-être des événements personnalisés) et utiliser $rootScope.$on
fonction pour ajouter l'auditeur d'événement.
vous devez utiliser le Service , parce que $rootscope
est l'accès à partir de l'Application entière , et il augmente la charge, ou youc utiliser les rootparams si vos données ne sont pas plus.
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 pouvez le faire en utilisant des événements angulaires qui est $emit et $broadcast. Selon nos connaissances, il s'agit de la meilleure façon, efficace et efficiente.
tout d'abord, nous appelons une fonction à partir 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 au lieu de $scope. Utilisez votre contrôleur en conséquence.
commence angular 1.5 et c'est un développement basé sur les composants. La méthode recommandée pour que les composants interagissent est l'utilisation de la propriété "require" et de la propriété bindings (input/output).
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 alors utiliser les méthodes du composant racine dans votre composant enfant:
$ctrl.api.addWatchedBook('ES6 - The Essentials');
c'est la fonction de contrôleur du composant racine:
function addWatchedBook(bookName){
booksWatched.push(bookName);
}
voici un aperçu complet de l'architecture: Component Communications