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);
    }
}
462
demandé sur Shashank Agrawal 2012-06-29 00:58:29
la source

19 ответов

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. $broadcast performances are identical to $emit with angular 1.2.16

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

jsperf results

451
répondu Christoph 2016-07-21 01:43:09
la source

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

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é.

102
répondu poshest 2017-05-23 14:54:57
la source

utilisant $rootScope.$ diffuser et $ scope.$ on pour une communication PubSub.

Aussi, voir ce post: AngularJS – la Communication Entre les Contrôleurs

52
répondu Renan Tomal Fernandes 2013-09-05 20:36:50
la source

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
        });
    }
    
42
répondu Singo 2014-02-17 07:30:50
la source

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:

Messaging Service

20
répondu Ryan Schumacher 2012-07-11 00:47:26
la source

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.

15
répondu numan salati 2012-08-18 08:30:08
la source

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>
14
répondu Load Reconn 2015-08-02 06:03:37
la source

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).

Ref: Lien docs angulaires ici

8
répondu pkbyron 2015-12-24 06:54:32
la source

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 /

5
répondu jcreamer898 2014-05-08 02:55:31
la source

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
]
3
répondu Oto Brglez 2014-07-31 13:37:44
la source

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');
3
répondu shikhar chauhan 2015-12-24 06:56:54
la source

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>
2
répondu Peter Drinnan 2013-07-30 23:51:35
la source

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

plus d'informations ici

0
répondu Prashobh 2014-03-22 09:09:42
la source

je vais créer un service et utiliser la notification.

  1. créer une méthode dans le service de Notification
  2. Créer une méthode générique pour la diffusion de notification dans le Service de Notification.
  3. du contrôleur source appeler le service de notification.Méthode. Je passe également l'objet correspondant à persister si nécessaire.
  4. dans la méthode, je persiste données dans le service de notification et appel Générique méthode de notification.
  5. 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

0
répondu rahul 2014-08-29 08:45:50
la source

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.

0
répondu Shivang Gupta 2014-12-30 12:19:28
la source

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.

0
répondu abhaygarg12493 2015-12-24 06:55:57
la source
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);
}
0
répondu Amin Rahimi 2015-12-24 06:56:40
la source

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.

0
répondu Peeyush Kumar 2017-05-25 16:16:41
la source

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

0
répondu kevinius 2017-06-11 20:01:46
la source

Autres questions sur angularjs scope