简体   繁体   中英

How do I keep track of events in AngularJS?

I have an app that has items, and you can do things like add new items, update the text of an item, move the item to a different folder, etc.

I have an items factory that holds all the items as plain objects inside an array, and the factory returns a singleton that has various methods, like get() , set() , etc.

To add some context to the question, I'm working with Node.js and MongoDB as well.

Anyway, due to all the various factories I have, like items , folders , and all the various controllers for different views, I am relying heavily on events. To give some examples:

// 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?

I agree that these item events should belong to the event source. You could implement a observer pattern in the item factory that hides the dependency on $rootScope for event listeners. This way the event key itself is a private detail of the item factory, and the subscription to the event is made explicit by calling a dedicated function for it. This approach makes your code more independent of $rootScope and easier to maintain than an event name convention (thinking about usages search for the specific event subscription method vs. usages of $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); }); 

complete example: http://jsfiddle.net/8LtyB/32/

If all you want is a way to create a separate object for containing the names of events, why not use a service?

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

This is essentially what you had before when you were suggesting using a factory to contain the event definitions, except that a service is a single object instance, and is instantiated at module start-up. In contrast, a factory creates a new instance when injected into a controller. ( Here's a good SO post on the difference between services and factories )

You can inject this service into your controllers or directives:

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

This gives you a nice place to centralize your event name definitions. As a side note, some people hold to the convention of using all lowercase when defining event names (so itemupdate instead of itemUpdate ). Hope this helps!

You can use the following:

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

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

src: https://github.com/angular/angular.js/issues/6043

assuming these $scope.$emit works like jquery events I would suggest you name your emits to be generic for example in you database update simply do this:

$rootScope.$emit('Created')

then in your items controller do this :

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

then you can wire to the created event in any of your controllers and its name is generic. The .item should add a namespace. if you make all of your events in your items controller have the .item name space you should be able to do a

$rootScope.$off('item')

This will clear up memory leaks

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM