4

How do I cleanup $rootScope.$on event subscriptions from inside a service?

I have a Factory that is being initialized in different controllers in my AngularJS application, where a $rootScope.$on event subscription is being declared. My problem is, when the controller is destroyed, the event is not cleaned up. I have done reading and found that with controllers and directives you can setup a $watch on $destroy for cleanup, but what about for services? How do you clean up services?

Here's a basic Plunker of my problem: http://plnkr.co/edit/dY3BVW. Click the 'create child' button a bunch of time to initialize a child controller that will initialize a factory with the $rootScope.$on. With the browser console open when you the click 'broadcast', you will see a bunch of event subscriptions being fired, event after the child controllers were destroyed.

Snippet from Plunker:

// setup rootScope on in Factory and create multiple instances
// to show that event subscriptions are not cleaned up
// TODO: how do you cleanup in factory?
app.factory('HelloWorldFactory', function($rootScope) {

  // setup event subscription on initialization
  function HelloWorldFactory() {
    console.log('init helloWorldFactory');

    var rootScopeOn = $rootScope.$on('test', function() {
      console.log('test sub', Math.random());
    });
  }

  return HelloWorldFactory;
});

// child ctrl will init a new factory instance
app.controller('ChildCtrl', function($scope, HelloWorldFactory) {
  $scope.name = 'Child';

  var helloWolrdFactory = new HelloWorldFactory();
});
user12121234
  • 2,519
  • 2
  • 25
  • 27
  • Would also like to know, leftover event listeners can cause some really nasty behavior. – twDuke Aug 13 '15 at 12:52
  • @twDuke I basically wrapped $on subscription event in a boolean conditional, and then only initialized once by setting an isLoaded variable to true on the first pass. – user12121234 Aug 13 '15 at 17:43

1 Answers1

4

You can kill the sentinel before creating the new event. The rootScopeOn variable will stay untouched after creating new controllers, so you can kill it every time you initialize it again.

app.factory('HelloWorldFactory', function($rootScope) {

  var rootScopeOn;

  // setup event subscription on initialization
  function HelloWorldFactory() {
    console.log('init helloWorldFactory');
    if (typeof rootScopeOn === 'function') {
      rootScopeOn();
    }

    rootScopeOn = $rootScope.$on('test', function() {
      console.log('test sub', Math.random());
    });
  }

  return HelloWorldFactory;
});

// child ctrl will init a new factory instance
app.controller('ChildCtrl', function($scope, HelloWorldFactory) {
  $scope.name = 'Child';

  var helloWolrdFactory = new HelloWorldFactory($scope);
});
Pepijn
  • 1,204
  • 1
  • 11
  • 25
  • This is in the right direction, however I have multiple Controllers and each is initializing it's own instance of the Factory. So with only one rootScopeOn variable and multiple instances to manage, this is running over itself and deleting all but the latest initialized Factory. – user12121234 Nov 12 '14 at 20:32
  • I changed the Plunker to make this more clear. Also, to make things more confusing for me, this service is actually called from prototypically inherited model service class across different children model classes, and so distinguishing between them at the parent level where the factory is called may be tough. – user12121234 Nov 12 '14 at 20:40