473 votes

Quelle est la bonne façon de communiquer entre contrôleurs dans AngularJS ?

Quelle est la manière correcte de communiquer entre les contrôleurs ?

J'utilise actuellement un horrible fondant 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);
    }
}

36 votes

Totalement discutable, mais en Angular, vous devriez toujours utiliser $window au lieu de l'objet fenêtre natif de JS. De cette façon, vous pouvez le stub dans vos tests :)

1 votes

Veuillez consulter mon commentaire dans la réponse ci-dessous à propos de cette question. $broadcast n'est plus plus plus cher que $emit. Voir le lien jsperf que j'ai référencé ici.

459voto

Christoph Points 4662

Modifier : Le problème abordé dans cette réponse a été résolu dans angular.js. version 1.2.7 . $broadcast évite maintenant les bulles sur les scopes non enregistrés et s'exécute aussi rapidement que $emit. $broadcast performances are identical to $emit with angular 1.2.16

Donc, maintenant vous pouvez :

  • utiliser $broadcast de la $rootScope
  • écouter en utilisant $on du local $scope qui doit être informé de 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 . La première solution peut entraîner de graves problèmes de performance, comme l'a souligné @numan. Cela est dû au fait que l'événement va se répercuter sur l'ensemble de la chaîne. tous les champs d'application.

Cependant, cette dernière (utilisant $rootScope.$emit + $rootScope.$on ) fait no en souffrent et peuvent donc être utilisés comme un canal de communication rapide !

D'après la documentation angulaire de $emit :

Distribue un nom d'événement vers le haut dans la hiérarchie de l'étendue, en notifiant l'utilisateur enregistré.

Puisqu'il n'y a pas de portée au-dessus de $rootScope il n'y a pas de bulles. Il est totalement sûr à utiliser $rootScope.$emit() / $rootScope.$on() comme un EventBus.

Cependant, il y a un problème lorsque vous l'utilisez à partir des contrôleurs. Si vous liez directement à $rootScope.$on() à l'intérieur d'un contrôleur, vous devrez nettoyer vous-même la liaison lorsque votre contrôleur local $scope est détruit. En effet, 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 aurait pour conséquence que les liaisons s'additionnent et créent des fuites de mémoire partout :)

Pour vous désenregistrer, il suffit d'écouter sur votre $scope 's $destroy et ensuite 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 que ce n'est pas vraiment une spécificité d'Angular, car cela s'applique aussi à d'autres implémentations d'EventBus, que vous devez nettoyer les ressources.

Cependant, vous peut vous faciliter la vie pour ces cas. Par exemple, vous pouvez faire un patch singe $rootScope et lui donner un $onRootScope qui souscrit aux événements émis sur le $rootScope mais aussi de nettoyer directement le gestionnaire lorsque l'option locale $scope est détruit.

La façon la plus propre de faire un "monkey patch" sur le $rootScope pour fournir ces $onRootScope se fera par le biais d'un décorateur (un bloc d'exécution fera probablement aussi bien l'affaire, mais ne le dites à personne).

Pour s'assurer que le $onRootScope n'apparaît pas de manière inattendue lors de l'énumération de la propriété $scope nous utilisons Object.defineProperty() et mettre enumerable a 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 du contrôleur ci-dessus peut être simplifié :

angular
    .module('MyApp')
    .controller('MyController', ['$scope', function MyController($scope) {

            $scope.$onRootScope('someComponent.someCrazyEvent', function(){
                console.log('foo');
            });
        }
    ]);

En conclusion de tout ceci, je vous conseille vivement d'utiliser $rootScope.$emit + $scope.$onRootScope .

En fait, j'essaie de convaincre l'équipe d'Angular de résoudre ce problème dans le noyau d'Angular. Il y a une discussion en cours ici : https://github.com/angular/angular.js/issues/4574

Voici un jsperf qui montre l'impact d'une perforation $broadcast apporte à la table dans un scénario décent avec seulement 100 $scope 's.

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

jsperf results

0 votes

J'essaie de faire votre deuxième option, mais j'obtiens une erreur : Uncaught TypeError : Impossible de redéfinir la propriété : $onRootScope à l'endroit où je fais le Object.defineProperty....

0 votes

J'ai peut-être fait une erreur en le collant ici. Je l'utilise en production et il fonctionne très bien. J'y jetterai un coup d'œil demain :)

0 votes

@Scott Je l'ai collé mais le code était déjà correct et c'est exactement ce que nous utilisons en production. Pouvez-vous vérifier que vous n'avez pas de faute de frappe sur votre site ? Puis-je voir votre code quelque part pour faciliter le dépannage ?

107voto

poshest Points 172

El meilleure réponse Il s'agissait d'un contournement d'un problème Angular qui n'existe plus (du moins dans les versions >1.2.16 et "probablement antérieures") comme l'a mentionné @zumalifeguard. Mais je me retrouve à lire toutes ces réponses sans solution réelle.

Il me semble que la réponse devrait être maintenant

  • utiliser $broadcast de la $rootScope
  • écouter en utilisant $on du local $scope qui doit être informé de l'événement

Donc, pour 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

Si vous enregistrez le récepteur sur le serveur local $scope il sera détruit automatiquement par $destroy lui-même lorsque le contrôleur associé est supprimé.

1 votes

Savez-vous si ce même modèle peut être utilisé avec le controllerAs syntaxe ? J'ai pu utiliser $rootScope dans l'abonné pour écouter l'événement, mais j'étais juste curieux de savoir s'il y avait un modèle différent.

3 votes

@edhedges Je suppose que vous pourriez injecter la $scope explicitement. John Papa écrit sur le fait que les événements sont une "exception" à sa règle habituelle de garder $scope "hors" de ses contrôleurs (j'utilise des guillemets car comme il le mentionne Controller As a toujours $scope c'est juste sous le capot).

0 votes

Par "sous le capot", voulez-vous dire que vous pouvez toujours y accéder par l'injection ?

54voto

Utilisation de $rootScope.$broadcast et $scope.$on pour une communication PubSub.

Voir aussi ce billet : AngularJS - Communiquer entre contrôleurs

3 votes

Cette vidéo réinvente simplement $rootScope y $watch . Pas sûr que ce soit une amélioration.

42voto

Singo Points 181

Puisque defineProperty a un problème de compatibilité avec les navigateurs, 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
        });
    }

7 votes

+1 pour la désinscription automatique lorsque l'étendue est détruite.

6 votes

J'aime cette solution. J'ai fait deux changements : (1) permettre à l'utilisateur de passer des 'données' au message emit (2) rendre le passage de 'scope' optionnel afin que cela puisse être utilisé dans les services singleton ainsi que les contrôleurs. Vous pouvez voir ces changements implémentés ici : gist.github.com/turtlemonvh/10686980/

20voto

Ryan Schumacher Points 1046

Lien avec la grille a posté un PubSub qui semble être assez bien conçue. Le service peut être trouvé, ici .

Egalement un schéma de leur service :

Messaging Service

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X