angularJS - 非依赖服务之间的通信

时间:2013-03-10 13:53:15

标签: angularjs

我是棱角分明的新人并遇到了一个陷阱22:

事实:

  1. 我有一个记录我的东西的服务(我的记录器)。

  2. 我已经用我自己的实现替换$ ExceptionHandler(angular),它将未捕获的异常转发给my-logger服务

  3. 我有另一项服务,推送服务,只要在我的应用程序中使用“my-logger”记录致命消息时,需要通知该消息。

  4. 问题:

    我不能让'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),这是不可能的,如上所示。

    我的问题:

    是否有一种有角度的方式让两个服务通过第三方实体进行通信?

    知道如何解决这个问题吗?

5 个答案:

答案 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;
            };
        });