Page title pattern with Ember

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:

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:

// app/overrides/route.js


import Ember from 'ember';

Ember.Route.reopen({
  actions: {
    didTransition() {
      var self = this;
      if(self.get('pageHeading')) {
        // We are setting our page title on the application controller.

        self.controllerFor('application').set('pageHeading', self.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/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 class="container">
    <aside class="sidebar">...</aside>
    <article class="main">
        <heading class="heading">
            <h1>{{pageHeading}}</h1>
        </heading>
        <section class="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.