Josh Beam Josh Beam - 3 months ago 9
AngularJS Question

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?

Answer

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/

Comments