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


Quelle est la manière correcte de communiquer entre les 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);
    }
}

462
2018-06-28 20:58


origine


Réponses:


modifier: Le problème abordé dans cette réponse a été résolu dans angular.js version 1.2.7. $broadcast évite maintenant de bouillonner sur des étendues non enregistrées et fonctionne aussi vite que $ emit. $broadcast performances are identical to $emit with angular 1.2.16

Donc, maintenant vous pouvez:

  • utilisation $broadcast du $rootScope
  • écouter en utilisant $on  du local $scope qui a besoin de savoir sur l'événement

Réponse originale ci-dessous

Je conseille fortement de ne pas utiliser $rootScope.$broadcast + $scope.$on mais plutôt $rootScope.$emit+ $rootScope.$on. Le premier peut causer de sérieux problèmes de performance, comme l'a souligné @numan. C'est parce que l'événement se déroulera à travers tout étendues.

Cependant, ce dernier (utilisant $rootScope.$emit + $rootScope.$on) Est-ce que ne pas souffrir de cela et peut donc être utilisé comme un canal de communication rapide!

De la documentation angulaire de $emit:

Distribue un nom d'événement vers le haut à travers la hiérarchie de portée notifiant le nom de domaine enregistré

Comme il n'y a pas de portée ci-dessus $rootScope, il n'y a pas de bulles. Il est totalement sûr à utiliser $rootScope.$emit()/ $rootScope.$on() en tant que EventBus.

Cependant, il y en a un quand vous l'utilisez depuis les contrôleurs. Si vous vous liez directement à $rootScope.$on() à partir d'un contrôleur, vous devrez nettoyer la liaison vous-même lorsque votre 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 se traduirait par une agrégation des liaisons, créant éventuellement des fuites de mémoire partout :)

Pour vous désinscrire, écoutez simplement votre $scopede $destroy événement, puis appelez 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 chose spécifique angulaire car cela s'applique aussi aux autres implémentations d'EventBus, que vous devez nettoyer les ressources.

Cependant, vous pouvez Facilitez-vous la vie pour ces cas. Par exemple, vous pouvez sonner le patch $rootScope et lui donner un $onRootScope qui souscrit aux événements émis sur le $rootScope mais nettoie aussi directement le gestionnaire lorsque le local $scope est détruit.

La manière la plus propre de mettre des singes sur le patch $rootScope pour fournir de tels $onRootScope méthode serait à travers un décorateur (un bloc de course le fera probablement bien aussi bien mais pssst, ne le dites à personne)

S'assurer que le $onRootScope propriété n'apparaît pas inattendue lors de l'énumération sur $scope nous utilisons Object.defineProperty() Et mettre enumerable à false. Gardez à l'esprit 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, le code du 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 un résultat final de tout cela, je vous conseille fortement d'utiliser $rootScope.$emit + $scope.$onRootScope.

Btw, j'essaye de convaincre l'équipe angulaire pour adresser le problème dans le noyau angulaire. Il y a une discussion en cours ici: https://github.com/angular/angular.js/issues/4574

Voici un jsperf qui montre combien d'impact de perf $broadcastapporte à la table dans un scénario décent avec seulement 100 $scope's.

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

jsperf results


450
2017-09-26 13:15



le meilleure réponse voici un travail autour d'un problème angulaire qui n'existe plus (du moins dans les versions> 1.2.16 et "probablement plus tôt") comme l'a mentionné @zumalifeguard. Mais je suis laissé à lire toutes ces réponses sans une solution réelle.

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

  • utilisation $broadcast du $rootScope
  • écouter en utilisant $on  du local $scope qui a besoin de savoir sur l'événement

Donc 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

Si vous enregistrez l'écouteur sur le site local $scope, ce sera détruit automatiquement par $destroy se lorsque le contrôleur associé est supprimé.


102
2018-06-28 21:17



En utilisant $ rootScope. $ diffusion et $ scope. $ on pour une communication PubSub.

Aussi, voir ce post: AngularJS - Communication entre les contrôleurs


52
2018-02-17 03:30



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 utilisez-le dans un contrôleur comme celui-ci:

  • 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
        });
    }
    

42
2017-07-10 20:47



GridLinked posté un PubSub solution qui semble être plutôt bien conçue. Le service peut être trouvé, ici.

Aussi un schéma de leur service:

Messaging Service


20
2017-08-18 04:30



En fait, l'utilisation d'émission et de diffusion est inefficace car l'événement fait des bulles dans la hiérarchie de la portée, ce qui peut facilement se dégrader en performance de mise en bouteille pour une application complexe.

Je suggère d'utiliser un service. Voici comment je l'ai récemment mis en œuvre dans l'un de mes projets - https://gist.github.com/3384419.

Idée de base - enregistrer un bus pubsub / événement en tant que service. Ensuite, injectez cet événement sur le bus pour vous abonner ou publier des événements / sujets.


15
2017-10-14 15:10



En utilisant les méthodes get et set dans un service, vous pouvez transmettre des messages entre 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 ça

<div ng-controller='FirstController'>  
</div>

<div ng-controller='SecondController'>
    {{rslt}}
</div>

14
2018-05-11 07:08