Updated October 2026: The gotcha still applies to current Ember; added an Octane (native class) resetController example and marked the {{render}} helper section as removed since Ember 3.0.

I. Controllers are singletons

Controllers in Ember are singletons. When the user leaves a page and goes to another one, the controller is not torn down. It lives on, keeping its properties. This makes sense for a framework that aims to create long-lived, rich client-side applications, but it’s something to watch out for when developing Ember applications.

II. Understanding the problem

For example, let’s consider a controller ChartController with a property changedCalculation, which indicates whether the calculation of the chart has been changed by the user. If changedCalculation is true, an Apply button is displayed below the calculation input. The property is set when the input changes:

changedCalculation: function () {
  this.set('controller.changedCalculation', true);
}
{{#if changedCalculation}}
  <button class="btn btn-primary size-mini btn-ctrl pull-right" {{action 'applyCalculation'}}>
    {{i18n js.charts.daten_tab.insert}}
  </button>
{{/if}}

Now the user edits the calculation of chart A and, without applying it, navigates to chart B. Both charts use the same route, so Ember only swaps the model: the chart data is rerendered, but the controller instance is the same one, and nothing resets changedCalculation. It is still true, so the Apply button is still visible on chart B even though the user has not changed anything there.

III. How to fix

Here are some solutions depending on the specific case:

  1. setupController: A hook you can use to set up the controller for the current route. This method is called with the controller for the current route and the model supplied by the model hook.
  2. resetController: A hook you can use to reset controller values either when the model changes or the route is exiting. (It has existed since Ember 1.7.)
  3. deactivate: This hook is executed when the router completely exits this route. It is not executed when the model for the route changes.

Example:

setupController: function(controller, model) {
  controller.set('changedCalculation', false);
  this._super.apply(this, arguments);
}

The same fix with native classes (Ember Octane and later), using resetController. It receives isExiting, which is true when the route is being left and false when only the model changed; for the chart case we reset in both situations:

// app/routes/chart.js
import Route from '@ember/routing/route';

export default class ChartRoute extends Route {
  resetController(controller, isExiting, transition) {
    // runs when the route exits *and* when only the model changes (chart A -> chart B)
    controller.changedCalculation = false;
  }
}
// app/controllers/chart.js
import Controller from '@ember/controller';
import { tracked } from '@glimmer/tracking';

export default class ChartController extends Controller {
  @tracked changedCalculation = false;
}

IV. What we can do (legacy: the {{render}} helper)

The {{render}} helper was deprecated in Ember 2.x and removed in Ember 3.0 (deprecation guide) in favor of components. In current Ember, move this kind of self-contained, data-loading UI into a component. The section is kept for readers maintaining Ember 2.x code.

The render helper renders a combination of a controller and template, and optionally allows you to provide a specific model to be set to the content property of the controller. If you do not provide a model, the singleton instance of the controller will be used.

Example:

{{render 'follow-authors'}}
loadData: Ember.on('init', function() {
  this.get('store').query('author', {
    type: 'suggest_authors'
  }).then((authors) => {
    this.set('authors', authors);
  });
})

In this example, the data of the authors property only loads once during the initial rendering.

Source:

  1. Ember Gotcha: Controllers Are Singletons
  2. Ember API: Route#resetController