'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:
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:
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):
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:
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:
<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
:
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.