Při komunikaci máme 2 subjekty - ten, kdo upozorňuje nebo publikuje změny (publisher) a ten, kdo na tyto změny poslouchá a reaguje (subscriber).
1) Způsob komunikace pomocí $rootScope jsem viděl nejčastěji popsané. Základní myšlenkou je použít nejvýše nadřazený $rootScope k vyslání události všem podřazeným posluchačům. Pro vyslání události jsou k dispozici dvě různé metody, které můžeme použít.
První z nich je $broadcast(). Podle dokumentace (https://docs.angularjs.org/api/ng/type/$rootScope.Scope) tato metoda propaguje směrem od rodiče k potomkům (shora dolů). :
http://plnkr.co/ZESyGrcUojE74wqNMt4y
.controller('PubController', function ($rootScope) { this.doSthOnClickAndNotifyOthers = function (valueToPass) { $rootScope.$broadcast("pubController.notifyHello", valueToPass); // do sth else }; }) .controller('SubController', function ($scope) { $scope.$on("pubController.notifyHello", function (event, value) {
}); });
Povšimněte si u subscriber controlleru registraci k poslechu události pomocí $scope.$on(). Protože $scope je potomkem $rootScope, dostane upozornění v případě, že se zavolá $broadcast() na rodiči, tedy na $rootScope.
Druhá metoda $emit() propaguje událost opačným směrem než $broadcast(), tedy směrem zdola nahoru. V dřívějších verzích AngularJS se upřednostňoval tento způsob, protože $broadcast() volal i $scope, které neposlouchaly na danou událost, a proto byl výrazně pomalejší (http://stackoverflow.com/questions/11252780/whats-the-correct-way-to-communicate-between-controllers-in-angularjs/19498009#19498009). Nyní už by měla být chyba opravena.
http://plnkr.co/F8bkF7YEpwKAloTWZCHC
.controller('PubController', function ($rootScope) { this.doSthOnClickAndNotifyOthers = function (valueToPass) { $rootScope.$emit("pubController.notifyHello", valueToPass); // do sth else }; }) .controller('SubController', function ($rootScope) { var thisController = this; thisController.values = []; $rootScope.$on("pubController.notifyHello", function (event, value) { thisController.values.push(value); }); });
Hlavním rozdílem mezi $emit() a $broadcast() je u registrace subscriber na událost. U $emit() je použito $rootScope místo $scope. Důvodem je propagace události pomocí $emit() směrem nahoru. Protože $rootScope je rodič všech jiných $scope a je tedy nejvýše v hierarchii Scope, tak nemůžeme pro registraci posluchače použít nic jiného než zase $rootScope. Nevýhoda této metody je nutnost odregistrovat posluchač v případě, že zanikne daný controller. AngularJS automaticky odregistruje všechny události navěšené na $scope controlleru při jeho zániku. Z tohoto důvodu u $broadcast() nebylo potřeba ručně odregistrovat. Z $rootScope se však události nikdy neodstraní a zůstávají zde do té doby, než skončí Angular. To může způsobit přetečení paměti. Více o řešení tohoto problému najdete v odkazu na Plunker.
2) http://plnkr.co/EUKADZ6T88Gws6XvqGXX
Tento způsob je zlepšení předchozí metody, protože odstraňuje závislost na $rootScope v controlleru a $rootScope se volá jen v servisních třídách. Další výhodu vidím v tom, že všechny názvy události lze shromáždit na jednom místě (např. zde v NotificationService) a v controllerech volat jen metody z této servisní třídy. O tomto způsobu jsem se dočetl zde: http://codingsmackdown.tv/blog/2013/04/29/hailing-all-frequencies-communicating-in-angularjs-with-the-pubsub-design-pattern/
NotificationService zajišťuje upozorňování událostí. Pro každou událost by tu měla být dvojice metod, jedna pro vyslání události a druhá pro poslouchání.
Controller, který je subscriber, předá do servisní třídy svůj $scope a fci, kterou se má zavolat, když přijde událost.
Tento způsob je zlepšení předchozí metody, protože odstraňuje závislost na $rootScope v controlleru a $rootScope se volá jen v servisních třídách. Další výhodu vidím v tom, že všechny názvy události lze shromáždit na jednom místě (např. zde v NotificationService) a v controllerech volat jen metody z této servisní třídy. O tomto způsobu jsem se dočetl zde: http://codingsmackdown.tv/blog/2013/04/29/hailing-all-frequencies-communicating-in-angularjs-with-the-pubsub-design-pattern/
.service('NotificationService', function ($rootScope) { var _HELLO_CLICK = "notifyHello"; this.onHello = function ($scope, handler) { $scope.$on(_HELLO_CLICK, function (event, args) { handler(args); }); }; this.notifyHello = function (args) { $rootScope.$broadcast(_HELLO_CLICK, args); }; return this; })
NotificationService zajišťuje upozorňování událostí. Pro každou událost by tu měla být dvojice metod, jedna pro vyslání události a druhá pro poslouchání.
.controller('PubController', function (NotificationService) { this.doSthOnClickAndNotifyOthers = function (valueToPass) { NotificationService.notifyHello(valueToPass); }; }) .controller('SubController', function ($scope, NotificationService) { NotificationService.onHello($scope, function (value) { // do sth when hello }); });
Controller, který je subscriber, předá do servisní třídy svůj $scope a fci, kterou se má zavolat, když přijde událost.
3) Promise API a notify()
http://plnkr.co/7clVRbi6OiV26OMtnIGH
Tento způsob jsem neviděl popsaný pro použití komunikace mezi controllery. Podobnou metodu jsem použil pro komunikaci pomocí websocketu, kdy se po celou dobu udržuje otevřené spojení se serverem a klient čeká a reaguje na odpovědi. Obdobně lze použít i pro případ, kdy potřebujete každých x sekund udělat AJAX dotaz na server a v případě změn aktualizovat stránku.
Prvně je potřeba vytvořit service třídu a v ní Deferred objekt pro událost, na kterou chceme reagovat.
var helloNotificationDefer = $q.defer();
Následně vytvoříme 2 metody - jednu pro publikování změn a druhou pro poslouchání těchto změn.
this.whenHello = function () { return helloNotificationDefer.promise; }; this.notifyHello = function (valueToPass) { helloNotificationDefer.notify(valueToPass); };
Metoda notify() v Deferred objektu, umožňuje opakovaně posílat události všem posluchačům. Na rozdíl od metod resolve() a reject() neukončuje Promise objekt, ale nechává ji otevřenou. Proto se také skvěle hodí např. pro komunikaci se serverem pomocí websocketu.
.controller('PubController', function (NotificationService) { this.doSthOnClickAndNotifyOthers = function (valueToPass) { NotificationService.notifyHello(valueToPass); // do sth else }; }).controller('SubController', function (NotificationService) { NotificationService.whenHello().then(null, null, function (value) { // do sth when got click event }) });<div ng-controller="PubController as pubCtrl"> <button ng-click="pubCtrl.doSthOnClickAndNotifyOthers('hello')">Click to pass 'hello'</button> </div>V publisher controlleru zavoláme metodu notifyHello(), pokud uživatel klikne na tlačítko. V subscriber controlleru musím poslouchat na danou událost. Pokud přijde událost, zavolá se fce, kterou jsem předal v části then(). Všimněte si předání dvou null argumentů v této metodě. První null je pro volání resolve(), druhý je pro reject() na Promise objektu a až třetí argument je pro notify() metodu. Protože resolve() ani reject() nikde nevoláme, není potřeba na tyto události reagovat,.
Mezi největší výhodu tohoto způsobu vidím v použití Promise API místo $rootScope. $rootScope je v Angularu něco jako window v JavaScriptu a pokud máme mnoho události, může to zaneřádit $rootScope.