Search code examples
javascriptember.jsdependency-injectionobserversember-controllers

Transitioning from a route doesn't remove exited route's controller observers in Ember.js


Here is a JSBin to illustrate the problem I have.

http://jsbin.com/patuje/4

I'm using a dependency injection to make a polling mechanism to an API, something like this

App.Poller = Ember.Object.extend({
  interval: function() {
    return 1000;
  }.property().readOnly(),

  schedule: function(f) {
    return Ember.run.later(this, function() {
      f.apply(this);
      this.set('timer', this.schedule(f));
    }, this.get('interval'));
  },

  stop: function() {
    this.set('running', false);
    Ember.run.cancel(this.get('timer'));
  },

  start: function() {
    if (!this.get('running')) {
      this.set('running', true);
      this.set('timer', this.schedule(this.get('onPoll')));
    }
  },

  onPoll: function() {
    Ember.Logger.log('basic poller overwrite with your method');
  }
});

App.register('poller:main', App.Poller);
App.inject('route', 'poller', 'poller:main');
App.inject('controller', 'poller', 'poller:main');

So that I can call start and stop polling from my routes & controllers.

I set up the parent route so that it intermittently polls the server for progress in the parent route like this (note the fetch syntax is from Ember Data beta 12 but works fine)

App.ParentRoute = Ember.Route.extend({
  setupController: function(controller, model) {
    store = this.store;   
    controller.set('model', model);

    this.poller.reopen({
      onPoll: function() {
        return store.fetch('mymodel', 1);
      }
    });  
  },

  model: function() {
    return this.store.find('mymodel', 1);
  }
});

I have various child routes as part of a step process which depends on the data received from the API polling, so in a child controller I set up an observer like this

App.ParentChild1Controller = Ember.Controller.extend({
  needs: ['parent'],
  progress: Ember.computed.alias('controllers.parent.progress'),

  pollingChild1: function() {
    progress = this.get('progress');

    Ember.Logger.log('called from pollingChild1 : ', progress);

    if (progress < 50) {
      this.poller.start();
    } else {
      this.transitionToRoute('parent.child2');
    }

  }.observes('progress').on('init')
});

It simply starts the polling and once the progress is above 50 transitions to the next route.

What I don't understand is why after transitioning to the new route this observer continues to be called?

If you look at the console from that JSBin when the route has changed it is still being called.

Any advice why this might be happening is very much appreciated.


Solution

  • The reason why the poller continues to run is because controllers are singletons which means that the controller will stick around for the lifetime of your application.

    In your case the parent controller is not even touched because all you are doing is transitioning to another chid of the parent route. But even in the case that you swapped out the model of the parent controller, the poller would still be running since its running on the controller which is a singleton.

    You can read more about it here: http://balinterdi.com/2014/06/26/ember-gotcha-controllers-are-singletons.html