Jaki jest prawidłowy sposób komunikacji między kontrolerami w AngularJS?

Jaki jest prawidłowy sposób komunikacji między kontrolerami?

Obecnie używam okropnej krówki z udziałem 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);
    }
}
Author: Shashank Agrawal, 2012-06-28

19 answers

Edit : problem rozwiązany w tej odpowiedzi został rozwiązany w trybie angular.js Wersja 1.2.7 . $broadcast Teraz unika bulgotania przez niezarejestrowane zakresy i działa tak szybko jak $emit. $ broadcast performances są identyczne z $emit z angular 1.2.16

Więc teraz możesz:

  • użyj $broadcast z $rootScope
  • listen using $on z lokalnego $scope to musi wiedzieć o wydarzeniu

Oryginalna Odpowiedź Poniżej

Radzę nie używać $rootScope.$broadcast + $scope.$on ale raczej $rootScope.$emit+ $rootScope.$on. Te pierwsze mogą powodować poważne problemy z wydajnością, jak podniósł @numan. Dzieje się tak dlatego, że zdarzenie przejdzie przez wszystkie zakresy.

Jednak ten ostatni (za pomocą $rootScope.$emit + $rootScope.$on) czyNie cierpią z tego powodu i dlatego może być używany jako kanał szybkiej komunikacji!

Z dokumentacji kątowej $emit:

Wysyła nazwę zdarzenia w górę poprzez hierarchię zakresu powiadamiając o zarejestrowany

Ponieważ nie ma zakresu powyżej $rootScope, nie ma bąbelków. Jest całkowicie bezpieczny w użyciu $rootScope.$emit()/ $rootScope.$on() jako EventBus.

Jest jednak jeden, gdy używa się go z kontrolerów. Jeśli bezpośrednio połączysz się z $rootScope.$on() z kontrolera, będziesz musiał sam wyczyścić Wiązanie, gdy twój lokalny $scope zostanie zniszczony. Wynika to z faktu, że kontrolery (w przeciwieństwie do usług) mogą być uruchamiane wielokrotnie w ciągu życia aplikacja, która skutkowałaby wiązaniami podsumowującymi ostatecznie tworzenie wycieków pamięci w całym miejscu:)

Aby się wyrejestrować, wystarczy nasłuchać zdarzenia $scope's $destroy, a następnie wywołać funkcję, która została zwrócona przez $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);
        }
    ]);

Powiedziałbym, że to nie jest tak naprawdę kątowa rzecz, ponieważ dotyczy również innych implementacji EventBus, że trzeba oczyścić zasoby.

Jednak Ty możesz ułatwić sobie życie w tych sprawach. Na przykład, można małpie patch $rootScope i dać mu $onRootScope, który subskrybuje zdarzenia emitowane na $rootScope, ale także bezpośrednio czyści obsługę, gdy lokalny $scope zostanie zniszczony.

Najczystszym sposobem na łatanie $rootScope, aby zapewnić taką metodę $onRootScope, byłoby użycie dekoratora (blok run prawdopodobnie zrobi to dobrze, ale pssst, nie mów nikomu)

Aby upewnić się, że właściwość $onRootScope nie pojawi się nieoczekiwanie podczas wyliczania nad $scope używamy Object.defineProperty() i ustawić enumerable na false. Pamiętaj, że możesz potrzebować podkładki 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;
        }]);
    }]);

Z tą metodą kod kontrolera z góry można uprościć do:

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

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

Więc jako ostateczny wynik tego wszystkiego gorąco radzę użyć $rootScope.$emit + $scope.$onRootScope.

Btw, staram się przekonać zespół angular do rozwiązania problemu wewnątrz angular core. Tutaj toczy się dyskusja: https://github.com/angular/angular.js/issues/4574

Oto jsperf to pokazuje, jak wiele perf impact $broadcastprzynosi do tabeli w przyzwoitym scenariuszu z zaledwie 100 $scope'S. {44]}

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

wyniki jsperf

 459
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-20 22:43:09

The top answer tutaj było obejście problemu kątowego, który już nie istnieje (przynajmniej w wersjach >1.2.16 i "prawdopodobnie wcześniej"), jak wspomniał @zumalifeguard. Ale pozostaje mi czytać te wszystkie odpowiedzi bez rzeczywistego rozwiązania.

Wydaje mi się, że teraz odpowiedź powinna brzmieć

  • użyj $broadcast z $rootScope
  • listen using $on z lokalnego $scope to musi wiedzieć o wydarzeniu

Więc do publikuj

// EXAMPLE PUBLISHER
angular.module('test').controller('CtrlPublish', ['$rootScope', '$scope',
function ($rootScope, $scope) {

  $rootScope.$broadcast('topic', 'message');

}]);

I subskrybuj

// EXAMPLE SUBSCRIBER
angular.module('test').controller('ctrlSubscribe', ['$scope',
function ($scope) {

  $scope.$on('topic', function (event, arg) { 
    $scope.receiver = 'got your ' + arg;
  });

}]);

Plunkers

Jeśli zarejestrujesz słuchacz na lokalnym $scope, zostanie on zniszczony automatycznie przez $destroy , gdy skojarzony kontroler zostanie usunięty.

 107
Author: poshest,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:57

Using $rootScope.$broadcast oraz $scope.$on za komunikację PubSub.

Zobacz też ten post: AngularJS-komunikacja między kontrolerami

 54
Author: Renan Tomal Fernandes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-05 16:36:50

Ponieważ defineProperty ma problem ze zgodnością przeglądarki, myślę, że możemy pomyśleć o użyciu usługi.

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

I użyj go w kontrolerze tak:

  • Kontroler 1

    function($scope, msgBus) {
        $scope.sendmsg = function() {
            msgBus.emitMsg('somemsg')
        }
    }
    
  • Controller 2

    function($scope, msgBus) {
        msgBus.onMsg('somemsg', $scope, function() {
            // your logic
        });
    }
    
 42
Author: Singo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-17 03:30:50

GridLinked opublikował rozwiązanie PubSub, które wydaje się być całkiem dobrze zaprojektowane. Serwis można znaleźć, tutaj.

Również schemat ich obsługi:

Serwis Komunikacyjny

 20
Author: Ryan Schumacher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-10 20:47:26

W rzeczywistości korzystanie z emit i broadcast jest nieefektywne, ponieważ zdarzenie pęka w górę iw dół hierarchii zakresu, co może łatwo obniżyć się do poziomu wydajności dla złożonej aplikacji.

Proponuję skorzystać z usługi. Oto jak ostatnio zaimplementowałem go w jednym z moich projektów - https://gist.github.com/3384419 .

Podstawowy pomysł-zarejestruj autobus pubsub/event jako usługę. Następnie wprowadź ten eventbus tam, gdzie chcesz subskrybować lub opublikować wydarzenia/tematy.

 15
Author: numan salati,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-18 04:30:08

Używając metod get I set w usłudze możesz bardzo łatwo przesyłać wiadomości między kontrolerami.

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

W HTML HTML możesz sprawdzić TAK

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

<div ng-controller='SecondController'>
    {{rslt}}
</div>
 14
Author: Load Reconn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-02 03:03:37

Odnośnie oryginalnego kodu-wygląda na to, że chcesz udostępnić dane między zakresami. Aby udostępnić dane lub stan pomiędzy $scope, docs sugeruje użycie usługi:

  • aby uruchomić kod bezstanowy lub Stanowy współdzielony między kontrolerami-użyj usługi kątowe.
  • aby utworzyć lub zarządzać cyklem życia inne komponenty(np. do tworzenia instancji usługi).

Ref: Angular Docs link tutaj

 8
Author: pkbyron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-24 03:54:32

Zacząłem używać poczty.js jako magistrala komunikatów pomiędzy kontrolerami.

Istnieje wiele korzyści z tego, jak szyna komunikatów, takich jak wiązania w stylu AMQP, sposób, w jaki postal może integrować się z ramkami iFrames i gniazdami sieciowymi i wiele innych rzeczy.

Użyłem dekoratora, aby ustawić pocztę na $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;
    }]);
});

Oto link do wpisu na blogu w temacie...
http://jonathancreamer.com/an-angular-event-bus-with-postal-js/

 5
Author: jcreamer898,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-07 22:55:31

Tak to robię z Factory / Services i simple dependency injection (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
Author: Oto Brglez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-31 09:37:44

Podobał mi się sposób, w jaki $rootscope.emit został użyty do osiągnięcia komunikacji międzykomunikacyjnej. Proponuję czyste i wydajne rozwiązanie bez zanieczyszczania globalnej przestrzeni.

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
Author: shikhar chauhan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-24 03:56:54

Oto szybki i brudny sposób.

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

    };

}

Oto przykładowa funkcja dodana do dowolnego kontrolera rodzeństwa:

$scope.$on('sinkallships', function() {

    alert('Sink that ship!');                       

});

I oczywiście tutaj jest Twój HTML:

<button ngclick="sendorders()">Sink Enemy Ships</button>
 2
Author: Peter Drinnan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-30 19:51:35

Starting angular 1.5 and it ' s component based development focus. Zalecanym sposobem interakcji komponentów jest użycie właściwości "require" i powiązanie właściwości (wejście/wyjście).

Komponent wymagałby innego komponentu (na przykład komponentu głównego) i otrzymałby odniesienie do jego kontrolera:

angular.module('app').component('book', {
    bindings: {},
    require: {api: '^app'},
    template: 'Product page of the book: ES6 - The Essentials',
    controller: controller
});

Możesz następnie użyć metod komponentu głównego w komponencie potomnym:

$ctrl.api.addWatchedBook('ES6 - The Essentials');

To jest główny kontroler komponentu Funkcja:

function addWatchedBook(bookName){

  booksWatched.push(bookName);

}

Oto kompletny przegląd architektury: Komunikacja komponentowa

 1
Author: kevinius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-11 17:01:46

Możesz uzyskać dostęp do tej funkcji hello w dowolnym miejscu w module

Controller one

 $scope.save = function() {
    $scope.hello();
  }

Drugi kontroler

  $rootScope.hello = function() {
    console.log('hello');
  }

Więcej informacji tutaj

 0
Author: Prashobh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-22 05:09:42

Stworzę usługę i użyję powiadomienia.

  1. Tworzenie metody w usłudze powiadomień
  2. Utwórz ogólną metodę nadawania powiadomień w usłudze powiadomień.
  3. z kontrolera źródłowego wywołaj notificationService.Metoda. W razie potrzeby przekazuję również odpowiedni obiekt do persist.
  4. w ramach metody zapisuję dane w usłudze powiadomień i wywołuję ogólną metodę powiadomień.
  5. w kontrolerze docelowym słucham ($scope.on) dla transmisja zdarzeń i danych dostępowych z usługi powiadamiania.

Ponieważ w każdym momencie Usługa powiadamiania jest singleton powinna być w stanie dostarczyć dane w całej

Hope this helps

 0
Author: rahul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-29 04:45:50

Możesz użyć usługi wbudowanej AngularJS $rootScope i wprowadzić tę usługę do obu kontrolerów. Następnie można nasłuchiwać zdarzeń wywołanych w obiekcie $rootScope.

$rootScope dostarcza dwóch dyspozytorów zdarzeń o nazwie $emit and $broadcast, które są odpowiedzialne za wysyłanie zdarzeń(mogą to być zdarzenia niestandardowe) i używają funkcji $rootScope.$on do dodania detektora zdarzeń.

 0
Author: Shivang Gupta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-30 09:19:28

Powinieneś skorzystać z usługi, ponieważ $rootscope jest dostęp z całej aplikacji i zwiększa obciążenie, lub używasz rootparams, jeśli Twoje dane nie są większe.

 0
Author: abhaygarg12493,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-24 03:55:57
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
Author: Amin Rahimi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-24 03:56:40

Można to zrobić za pomocą zdarzeń kątowych, które są $emit i $broadcast. Zgodnie z naszą wiedzą jest to najlepszy, skuteczny i skuteczny sposób.

Najpierw wywołujemy funkcję z jednego kontrolera.

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

    });
});

Możesz również użyć $rootScope zamiast $ scope. Użyj kontrolera odpowiednio.

 0
Author: Peeyush Kumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-25 13:16:41