web-dev-qa-db-fra.com

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

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);
    }
}
463
fadedbee

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

Alors, maintenant vous pouvez:

  • utilisez $broadcast à partir du $rootScope
  • écoutez en utilisant $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.

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

jsperf results

452
Christoph

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 

  • utilisez $broadcast à partir du $rootScope
  • écoutez en utilisant $onà partir du $scope local qui doit connaître 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 $scope local, il sera détruit automatiquement par $destroy lui-même lorsque le contrôleur associé sera supprimé. 

103
poshest

Utilisation de $ rootScope. $ Broadcast et $ scope. $ On pour une communication PubSub.

Voir aussi cet article: AngularJS - Communication entre contrôleurs

53

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
        });
    }
    
42
Singo

GridLinked posted a PubSub solution qui semble assez bien conçue. Le service peut être trouvé, ici .

Également un schéma de leur service:

Messaging Service

20
Ryan Schumacher

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.

15
numan salati

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>
14
Load Reconn

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:

  • Pour exécuter du code sans état ou avec état partagé sur plusieurs contrôleurs, utilisez plutôt les services angulaires
  • Instancier ou gérer le cycle de vie d'autres composants (par exemple, pour créer des instances de service).

Ref: Angular Docs lien ici

8
pkbyron

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/

5
jcreamer898

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
]
3
Oto Brglez

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');
3
shikhar chauhan

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>
2
Peter Drinnan

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

Plus d'infos ici

0
Prashobh

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

0
kevinius

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.

0
Shivang Gupta

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.

0
Peeyush Kumar
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
Amin Rahimi

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.

0
abhaygarg12493

Je vais créer un service et utiliser une notification.

  1. Créer une méthode dans le service de notification
  2. Créez une méthode générique pour diffuser une notification dans le service de notification.
  3. Depuis le contrôleur source, appelez notificationService.Method. Je passe également l'objet correspondant à persister si nécessaire.
  4. Dans la méthode, je conserve les données dans le service de notification et appelle la méthode de notification générique.
  5. Dans le contrôleur de destination, j'écoute ($ scope.on) l'événement de diffusion et accède aux données du service de notification. 

Comme à tout moment, Notification Service est un singleton, il devrait pouvoir fournir des données persistantes.

J'espère que cela t'aides

0
rahul