'Route variable' pattern with Ember

There are a couple of different libraries for Ember that add the ability to set the page title for each page in your application by simply creating a title property on each route. This is a great general purpose pattern for declarative information that needs to be set across routes. For example you might want to add breadcrumbs, or just a different heading on each page. This post will outline how to go about creating this pattern yourself.

If you have tried to set the page title on your Ember application, you have likely come across the following libraries:

These are great drop-in libraries that are also very easy to work with. For example, the latter library allows you to simply set a title property on every route in your application and the meta title will magically be set when you transition to it:

route.js
import Ember from 'ember';
 
export default Ember.Route.extend({
  ...
  title: "Hello World",
  ...
});

This is a great general purpose pattern. We simply set a property on every route and this property is automatically grabbed from the current route to perform some action transparently.

Let’s recreate this pattern with a simple example. Instead of setting the page title (the two libraries above have this requirement nailed), instead we’re going to create a pageHeading property that we can use to set a application-wide page heading that changes when you move from page to page (and route to route). To achieve this, we’re going to override the default Ember.Route object. Our overridden object will automatically search for the required property on itself and insert it transparently into the the application controller when the page transitions.

Override the default route object

First we create an app/overrides/ folder (it can be named whatever you like) and create a new file route.js within it:

route.js
import Ember from "ember";
 
Ember.Route.reopen({
  actions: {
    didTransition() {
      if (this.get("pageHeading")) {
        // We are setting our page title on the application controller.
        this.controllerFor("application").set(
          "pageHeading",
          this.get("pageHeading")
        );
      }
      return true; // bubble the event
    },
  },
});

Here we are simply reopening the default Ember.Route object and piggy-backing on the didTransition action from Ember.Route to check whether or not our current route has a pageHeading property set. If it does, we grab the application controller and set the pageTitle property on it so that it’s available in our application template.

Import our new route object

As it is, our application will never be aware of our updated Route object. We need to import it. We could try use an initializer to do this but as they can potentially run multiple times (as far as I’m aware) so it’s easier to simply import our module in app.js (via StackOverflow):

app.js
import Ember from "ember";
import Resolver from "./resolver";
import loadInitializers from "ember-load-initializers";
import config from "./config/environment";
 
// Override the default functionality of the Ember.Route for our own requirements
import "./overrides/route";
 
let App;
 
Ember.MODEL_FACTORY_INJECTIONS = true;
 
App = Ember.Application.extend({
  modulePrefix: config.modulePrefix,
  podModulePrefix: config.podModulePrefix,
  Resolver,
});
 
loadInitializers(App, config.modulePrefix);
 
export default App;

Add page titles

With our custom Route object now created and imported by default, we simply add page titles in our routes:

app/routes/my-page.js
import Ember from 'ember';
 
export default Ember.Route({
   ...
   pageHeading: "My New Page",
   ...
});

and then grab the pageHeading in our application template so that it’s displayed on every page:

app/templates/application.hbs
<div className="container">
  <aside className="sidebar">...</aside>
  <article className="main">
    <heading className="heading">
      <h1>{{pageHeading}}</h1>
    </heading>
    <section className="section">
      {{outlet}}
    </section>
  </article>
</div>

Extra: setting the property on the current controller

One caveat of the above approach is that we are setting the pageHeading property on the application controller only. This means if we try to access pageHeading in our my-page template it won’t exist. To fix this, simply change from this.controllerFor('application').set(...) to this.controller.set(...) in app/overrides/route.js:

app/overrides/route.js
import Ember from "ember";
 
Ember.Route.reopen({
  actions: {
    didTransition() {
      var self = this;
      if (self.get("pageHeading")) {
        this.controller.set("pageHeading", self.get("pageHeading"));
      }
      return true; // bubble the event
    },
  },
});

and you will now have access to the pageHeading variable on the currently active controller.