我是棱角分明的新人并遇到了一个陷阱22:
事实:
我有一个记录我的东西的服务(我的记录器)。
我已经用我自己的实现替换$ ExceptionHandler(angular),它将未捕获的异常转发给my-logger服务
我有另一项服务,推送服务,只要在我的应用程序中使用“my-logger”记录致命消息时,需要通知该消息。
问题:
我不能让'my-logger'依赖于'推送',因为它会创建循环依赖(因为'推送'使用$ http。圆圈:$ ExceptionHandler - > my-logger - >推送器 - > $ http - > $ ExceptionHandler ...)
我的尝试:
为了让这两个服务相互通信,我想在推送服务上使用$ watch:在$ rootscope上查看将在my-logger中更新的属性。 但是,当尝试在'my-logger'中使用$ rootScope时,为了更新'pusher'“监视”的属性,我在循环依赖上失败,因为事实证明$ rootscope依赖于$ ExceptionHandler(圆圈) :$ ExceptionHandler - > my-logger - > $ rootScope - > $ ExceptionHandler)。
试图找到一个选项,在运行时获取在其上下文“my-logger”服务中工作的范围对象。找不到这样的选择。
也不能使用广播,因为它需要my-logger才能访问范围($ rootScope),这是不可能的,如上所示。
我的问题:
是否有一种有角度的方式让两个服务通过第三方实体进行通信?
知道如何解决这个问题吗?
答案 0 :(得分:10)
使用充当通知/ pubsub服务的第3项服务:
.factory('NotificationService', [function() {
var event1ServiceHandlers = [];
return {
// publish
event1Happened: function(some_data) {
angular.forEach(event1ServiceHandlers, function(handler) {
handler(some_data);
});
},
// subscribe
onEvent1: function(handler) {
event1ServiceHandlers.push(handler);
}
};
}])
上面,我只显示一个事件/消息类型。每个附加事件/消息都需要自己的数组,发布方法和订阅方法。
.factory('Service1', ['NotificationService',
function(NotificationService) {
// event1 handler
var event1Happened = function(some_data) {
console.log('S1', some_data);
// do something here
}
// subscribe to event1
NotificationService.onEvent1(event1Happened);
return {
someMethod: function() {
...
// publish event1
NotificationService.event1Happened(my_data);
},
};
}])
Service2的编码方式与Service1类似。
请注意$ rootScope,$ broadcast和scope如何不与此方法一起使用,因为它们不需要与服务间通信。
通过上述实现,服务(一旦创建)保持订阅应用程序的生命周期。您可以添加处理取消订阅的方法。
在我当前的项目中,我使用相同的NotificationService来处理控制器范围的pubsub。 (如果感兴趣,请参阅Updating "time ago" values in Angularjs and Momentjs。)
答案 1 :(得分:6)
是的,使用事件和听众。
在您的记录器中'您可以在捕获新日志时广播事件:
$rootScope.$broadcast('new_log', log); // where log is an object containing information about the error.
而不是在你的' pusher'
中听取这个事件$rootScope.$on('new_log', function(event, log) {... //
这样您就不需要依赖。
答案 2 :(得分:1)
我已部分成功解决此案: 我使用$ injector创建了'my-logger'和'pusher'之间的依赖关系。 我在'my-logger'中使用$ injector并在“运行时”注入(意味着当它即将被使用而不是在服务声明时)推送服务在致命消息到达时。 只有在发送完成之前我还在“运行时”将$ http注入“推送器”时才能正常工作。
我的问题是为什么它在“运行时”中使用注入器而不是在服务的头部声明的依赖项?
我只有一个猜测: 这是一个时间问题: 当服务在“运行时”注入时,如果它已经存在(手段已经初始化,那么那里),那么不需要来获取并获取其所有依赖项,因此永远不会发现圆圈 并且永远不会停止执行。
我说错了吗?
答案 3 :(得分:0)
这是在服务和控制器之间发布/订阅多个事件的简便方法
.factory('$eventQueue', [function() {
var listeners = [];
return {
// publish
send: function(event_name, event_data) {
angular.forEach(listeners, function(handler) {
if (handler['event_name'] === event_name) {
handler['callback'](event_data);
}
});
},
// subscribe
onEvent: function(event_name,handler) {
listeners.push({'event_name': event_name, 'callback': handler});
}
};
}])
消费者和生产者
.service('myService', [ '$eventQueue', function($eventQueue) {
return {
produce: function(somedata) {
$eventQueue.send('any string you like',data);
}
}
}])
.controller('myController', [ '$eventQueue', function($eventQueue) {
$eventQueue.onEvent('any string you like',function(data) {
console.log('got data event with', data);
}])
.service('meToo', [ '$eventQueue', function($eventQueue) {
$eventQueue.onEvent('any string you like',function(data) {
console.log('I also got data event with', data);
}])
答案 4 :(得分:0)
您可以制作自己的通用事件发布者服务,并将其注入每项服务。
这是一个例子(我没有测试过,但你明白了):
.provider('myPublisher', function myPublisher($windowProvider) {
var listeners = {},
$window = $windowProvider.$get(),
self = this;
function fire(eventNames) {
var args = Array.prototype.slice.call(arguments, 1);
if(!angular.isString(eventNames)) {
throw new Error('myPublisher.on(): argument one must be a string.');
}
eventNames = eventNames.split(/ +/);
eventNames = eventNames.filter(function(v) {
return !!v;
});
angular.forEach(eventNames, function(eventName) {
var eventListeners = listeners[eventName];
if(eventListeners && eventListeners.length) {
angular.forEach(eventListeners, function(listener) {
$window.setTimeout(function() {
listener.apply(listener, args);
}, 1);
});
}
});
return self;
}
function on(eventNames, handler) {
if(!angular.isString(eventNames)) {
throw new Error('myPublisher.on(): argument one must be a string.');
}
if(!angular.isFunction(handler)) {
throw new Error('myPublisher.on(): argument two must be a function.');
}
eventNames = eventNames.split(/ +/);
eventNames = eventNames.filter(function(v) {
return !!v;
});
angular.forEach(eventNames, function(eventName) {
if(listeners[eventName]) {
listeners[eventName].push(handler);
}
else {
listeners[eventName] = [handler];
}
});
return self;
}
function off(eventNames, handler) {
if(!angular.isString(eventNames)) {
throw new Error('myPublisher.off(): argument one must be a string.');
}
if(!angular.isFunction(handler)) {
throw new Error('myPublisher.off(): argument two must be a function.');
}
eventNames = eventNames.split(/ +/);
eventNames = eventNames.filter(function(v) {
return !!v;
});
angular.forEach(eventNames, function(eventName) {
if(listeners[eventName]) {
var index = listeners[eventName].indexOf(handler);
if(index > -1) {
listeners[eventName].splice(index, 1);
}
}
});
return self;
}
this.fire = fire;
this.on = on;
this.off = off;
this.$get = function() {
return self;
};
});