如何在AngularJS中跟踪事件?

5
我有一个应用程序,其中包含项目,您可以执行诸如添加新项目、更新项目文本、将项目移动到不同文件夹等操作。
我有一个“items”工厂,它将所有项目作为纯对象存储在数组中,并返回一个单例,具有各种方法,例如“get()”、“set()”等。
为了给问题增加一些背景,我也在使用Node.js和MongoDB。
无论如何,由于我有许多不同的工厂,例如“items”、“folders”以及所有不同视图的各种控制器,我非常依赖事件。以下是一些示例:
// items factory
update: function(params) {
    // add to database, then...
    .then(function() {
        $rootScope.$emit('itemCreated');
    });
}

// items controller

// I need to refresh the items list in the scope
$rootScope.$on('itemCreated', function() { // when an item is added to the database
    $scope.items = items.getAll(); // retrieve all items from the items factory
});

These are their own kind of "subset" of events, in that they all pertain to "CRUD" operations on items.

But, I also have other events that I use. For example, I have an interceptor that listens to any requests and responses. I have a loading widget (an image of a spinning wheel) that uses a directive. This directive will show the loading widget when a request begins, and hide the loading widget when a request ends. This is also event based.

// on request
$rootScope.$emit(_START_REQUEST_);

// on any response
$rootScope.$emit(_END_REQUEST_);

I attempted to "modularize" these request and response events by simply making them constants.

.constant('_START_REQUEST_', '_START_REQUEST_');

I am trying to find a solution in order to "modularize" all my other events, like the events emitted on CRUD operations for items. One idea I've had is to define all of the item CRUD events inside the items factory:

events: {
    update: 'itemUpdate',
    create: 'itemCreated'
    // etc.
}

Then, I can simply inject my items factory into a controller, and reference events like so:

$rootScope.$on(items.events.update, function() {});

I also considered simply defining all events, regardless of whether they are interceptor events or item events, as constants in my app. However, it seemed like this solution directly coupled item events to the module itself, rather than to the items factory, which is where I feel they "belong".

Basically, the issue is that right now all my events definitions seem to be scattered around. My question is: what pattern or best practice would you recommend for modularizing and defining events in AngularJS?

4个回答

4

我认为这些项目事件应该属于事件源。您可以在项目工厂中实现一个观察者模式,以隐藏事件监听器对$rootScope的依赖关系。这样,事件键本身就是项目工厂的私有细节,并且通过调用专用函数来明确订阅事件。这种方法使您的代码更独立于$rootScope,比事件名称约定更易于维护(思考使用特定事件订阅方法的用途搜索与$rootScope.$emit / $on的用法):

angular.module('events', [])

.service('items', ['$rootScope', function($rootScope) {
  var createdEventKey = 'item.created';
    
  return {
      create: function () {
          $rootScope.$emit(createdEventKey, {"name": "aItemName"});
      },
      
      onCreated: function(callback, scope) {
          var unsubscribeFunction = $rootScope.$on(createdEventKey, function(event, payload) {
              callback(payload);
          });
          
          // allow to unsubscribe automatically on scope destroy to prevent memory leaks 
          if (scope) {
            scope.$on("$destroy", unsubscribeFunction);
          }
          
          return unsubscribeFunction;
      }   
  }
}])

.controller('TestController', function($scope, items) {
    items.onCreated(function (item) {
        console.log("Created: " + item.name);
    }, $scope);
});

完整示例:http://jsfiddle.net/8LtyB/32/


1
在这里实现观察者模式确实是一个非常优雅的方法+1!虽然我建议您更新答案,包括来自Fiddle的两个部分 - 包括注销函数的缺失返回值和控制器中适当的实现。 - conceptdeluxe
这是一个不错的例子,但我同意@conceptdeluxe的观点 - 请提供一个更新的例子,展示如何取消订阅以避免内存泄漏。 - Josef P.

0
如果您只是想创建一个包含事件名称的单独对象,为什么不使用服务呢?

myApp.service('itemEvents', function () {
  var events = {
    update: 'itemupdate',
    create: 'itemcreate',
    ...
  };
  return events;
});

这本质上就是你之前建议使用工厂来包含事件定义的方式,不同的是服务是一个单一的对象实例,并在模块启动时实例化。相比之下,当注入到控制器中时,工厂会创建一个新的实例。(这里有一篇关于服务和工厂区别的好文章)

你可以将此服务注入到你的控制器或指令中:

myApp.controller('ItemController', function ($scope, itemEvents) {
  $scope.on(itemEvents.update, function () { /* something interesting */ });
});

这为您提供了一个很好的地方来集中您的事件名称定义。顺便说一下,有些人遵循在定义事件名称时使用全小写的约定(因此使用itemupdate而不是itemUpdate)。希望能帮到您!


0
你可以使用以下内容:
app.config(function($provide) {
    $provide.decorator("$rootScope", function($delegate) {
        var Scope = $delegate.constructor;
        var origBroadcast = Scope.prototype.$broadcast;
        var origEmit = Scope.prototype.$emit;

        Scope.prototype.$broadcast = function() {
            console.log("$broadcast was called on $scope " + Scope.$id + " with arguments:",
                arguments);
            return origBroadcast.apply(this, arguments);
        };
        Scope.prototype.$emit = function() {
            console.log("$emit was called on $scope " + Scope.$id + " with arguments:",
                arguments);
            return origEmit.apply(this, arguments);
        };
        return $delegate;
    });
})

示例:http://plnkr.co/edit/cn3MZynbpTYIcKUWmsBi?p=preview

源代码:https://github.com/angular/angular.js/issues/6043


-1
假设这些 $scope.$emit 的工作方式类似于 jQuery 事件,我建议您将其命名为通用的,例如在数据库更新中只需执行以下操作:
$rootScope.$emit('Created')

然后在你的items控制器中这样做:

$rootScope.$on('Created.item', function() { // when an item is added to the database
    $scope.items = items.getAll(); // retrieve all items from the items factory
});

然后您可以在任何控制器中连接到创建的事件,其名称是通用的。 .item 应该添加一个命名空间。 如果您使项目控制器中的所有事件都具有 .item 命名空间,则应该能够执行

$rootScope.$off('item')

这将清除内存泄漏


1
假设这些$scope.$emit像jQuery一样工作...但实际上并不是这样! - gkalpak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接