AngularJS: двунаправленная связь между двумя областями / контроллерами через сервис
У меня есть довольно много сценариев, где мне нужны щелчки и т. Д., Чтобы вызвать поведение в другом месте на странице (сценарий односторонней связи). Теперь у меня есть потребность в двунаправленной коммуникации, где вещи, которые происходят в элементе A, могут изменять определенные свойства в области действия позади элемента B и наоборот. До сих пор я использовал $rootScope.$broadcast
чтобы облегчить это, но это похоже на излишество, и в итоге получается создание шаблона в обоих местах:
$scope.$on('event-name', function(event, someArg) {
if(someArg === $scope.someProperty) return;
$scope.someProperty = someArg;
});
$scope.$watch('someProperty', function(newValue) {
$rootScope.$broadcast('event-name', newValue);
});
Есть ли способ лучше? Я хотел бы связать две (или три, или N) области вместе с помощью службы, но я не вижу способа сделать это без названий магических событий и шаблонов.
2 ответа
Я не использовал это сам, но этот пост объясняет, в основном, как я это сделаю. Вот код, который иллюстрирует идею:
(function() {
var mod = angular.module("App.services", []);
//register other services here...
/* pubsub - based on https://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js*/
mod.factory('pubsub', function() {
var cache = {};
return {
publish: function(topic, args) {
cache[topic] && $.each(cache[topic], function() {
this.apply(null, args || []);
});
},
subscribe: function(topic, callback) {
if(!cache[topic]) {
cache[topic] = [];
}
cache[topic].push(callback);
return [topic, callback];
},
unsubscribe: function(handle) {
var t = handle[0];
cache[t] && d.each(cache[t], function(idx){
if(this == handle[1]){
cache[t].splice(idx, 1);
}
});
}
}
});
return mod;
})();
Обратите внимание на утечку памяти, если контроллеры "удаляются" без отписки.
Я думаю, что вы можете попробовать следующий сервис,
'use strict';
angular.module('test')
.service('messageBus', function($q) {
var subscriptions = {};
var pendingQuestions = [];
this.subscribe = function(name) {
subscriptions[name].requestDefer = $q.defer();
return subscriptions[name].requestDefer.promise; //for outgoing notifications
}
this.unsubscribe = function(name) {
subscriptions[name].requestDefer.resolve();
subscriptions[name].requestDefer = null;
}
function publish(name, data) {
subscriptions[name].requestDefer.notify(data);
}
//name = whom shd answer ?
//code = what is the question ?
//details = details abt question.
this.request = function(name, code, details) {
var defered = null;
if (subscriptions[name].requestDefer) {
if (pendingQuestions[code]) {
//means this question is already been waiting for answer.
//hence return the same promise. A promise with multiple handler will get
//same data.
defered = pendingQuestions[code];
} else {
defered = $q.defer();
//this will be resolved by response method.
pendingQuestions[code] = defered;
//asking question to relevant controller
publish(name, {
code: code,
details: details
});
}
} else {
//means that one is not currently in hand shaked with service.
defered = $q.defer();
defered.resolve({
code: "not subscribed"
});
}
return defered.promise;
}
//data = code + details
//responder does not know the destination. This will be handled by the service using
//pendingQuestions[] array. or it is preemptive, so decide by code.
this.response = function(data) {
var defered = pendingQuestions[data.code];
if (defered) {
defered.resolve(data);
} else {
//means nobody requested for this.
handlePreemptiveNotifications(data);
}
}
function handlePreemptiveNotifications() {
switch (data.code) {
//handle them case by case
}
}
});
Это может использоваться как шина сообщений в мультиконтроллерной связи. Он использует угловой обратный вызов notify() API обещания. Все участвующие контроллеры должны подписать сервис следующим образом:
angular.module('test')
.controller('Controller1', function($scope, messageBus) {
var name = "controller1";
function load() {
var subscriber = messageBus.subscribe(name);
subscriber.then(null, null, function(data) {
handleRequestFromService(data);
});
}
function handleRequestFromService(data) {
//process according to data content
if (data.code == 1) {
data.count = 10;
messageBus.respond(data);
}
}
$scope.$on("$destroy", function(event) {
//before do any pending updates
messageBus.unsubscribe(name);
});
load();
});
angular.module('test')
.controller('Controller2', function($scope, messageBus) {
var name = "controller2";
function load() {
var subscriber = messageBus.subscribe(name);
subscriber.then(null, null, function(data) {
handleRequestFromService(data);
});
}
function handleRequestFromService(data) {
//process according to data content
}
$scope.getHorseCount = function() {
var promise = messageBus.request("controller1", 1, {});
promise.then(function(data) {
console.log(data.count);
});
}
$scope.$on("$destroy", function(event) {
//before do any pending updates
messageBus.unsubscribe(name);
});
load();
});