如何在服务中封装单个和时间事件?

时间:2014-10-24 22:35:06

标签: javascript angularjs javascript-events angularjs-scope

我试图将事件封装在服务中,以便在控制器的范围被破坏时实现一个机制来订阅/取消订阅监听器。这是因为我一直在以下列方式使用rootScope。$:

if(!$rootScope.$$listeners['event']) {
    $rootScope.$on('event', function(ev, data){
        // do some...
    });
}

$scope.$on('$destroy', function(ev, data){
    // unsubscribe the listener
});

所以我只需要一个这个事件的监听器,我需要在控制器不再存活时删除现有的监听器,因为我之前注册的功能仍在被触发。

所以我需要在我的控制器上实现一个$ destroy事件监听器,以便在范围被破坏时销毁监听器,但是每次创建事件时我都不想这样做。 这就是为什么我想在我要封装事件的地方创建服务的原因。

angular.module('core').factory('event', [
    function() {
        var service = {};
        service.events = {};
        service.on = function(scope, eventId, callback) {
            scope.$on('$destroy', function(ev, other){
                //unsubscribe
            });
            service.events[eventId] = callback;
            // scope = null; I guess ?
        };
        service.emit = function(eventId, data){
            if (service.events[eventId])
                service.events[eventId](data);
            else
                return new Error('The event is not subscribed');
        };
        return service;
    }
]);

这可以使用 $ rootScope 而不是我自己的方法来完成,但是封装了 $ rootScope 的$ on和$ emit,但最后我还是这里有同样的问题。

所以这些是我的问题:

  1. 将范围参考值传递给服务是一种好习惯吗?
  2. $$被破坏是什么意思?当这是真的意味着angularJS没有对实例的内部引用?
  3. 我应该在我的服务中执行范围 = null以让GC删除对象或者angularJS是否处理显式删除?
  4. 有没有更好的方法来做我想要的事情?

1 个答案:

答案 0 :(得分:3)

您要完成的工作基本上是事件总线
您还很好地描述了当前实现的错误。 解决问题的另一种方法是使用您的总线(或任何其他事件总线)来装饰$ rootScope。方法如下:

app.config(function ($provide) {
$provide.decorator('$rootScope', ['$delegate', '$$bus', function ($delegate, $$bus) {
  Object.defineProperty($delegate.constructor.prototype, '$bus', {
    get: function () {
      var self = this;

      return {
        subscribe: function () {
          var sub = $$bus.subscribe.apply($$bus, arguments);

          self.$on('$destroy',
            function () {
              console.log("unsubscribe!");
              sub.unsubscribe();

            });
        },

        publish: $$bus.publish
      };
    },
    enumerable: false
  });

  return $delegate;
}]);
});

考虑以下$$总线实现(为简单起见保持基本):

app.factory('$$bus', function () {
  var api = {};
  var events = {};

  api.subscribe = function (event) {
    if (!events.hasOwnProperty(event.name)) {
      events[event.name] = [event];
    } else {
      events[event.name].push(event);
    }
    return {
      unsubscribe: function () {
        api.unsubscribe(event);
      }
    }
  };

  api.publish = function (eventName, data) {
    if (events.hasOwnProperty(eventName)) {
      console.log(eventName);

      angular.forEach(events[eventName], function (subscriber) {
        subscriber.callback.call(this, data);
      });
    }
  };

  api.unsubscribe = function (event) {
    if (events.hasOwnProperty(event.name)) {
      events[event.name].splice(events[event.name].indexOf(event), 1);
      if (events[event.name].length == 0) {
        delete events[event.name];
      }
    }
  };

  return api;
});

现在,您所要做的只是订阅发布事件。取消订阅将自动进行(当$ scope被销毁时):

  $scope.$bus.subscribe({
    name: 'test', callback: function (data) {
      console.log(data);
    }
  });

稍后发布活动:

  $scope.$bus.publish('test', {name: "publishing event!"});

重要要点是事件本身订阅了每个$ scope而不是$ rootScope。这就是你"知道"哪个$范围要发布。

我认为它回答了你的问题。考虑到这一点,您显然可以使此机制更复杂(例如,在视图路由时释放控制器事件侦听器,仅自动取消订阅某些事件等)。 祝你好运!

**此解决方案采用Here形式,它使用不同的总线框架(除此之外它是相同的)。