One of the nicest features of Ember.js is the ability to easily create a route and a page template and be able to browse to it in seconds. A good understanding of how routing works can help hunt down bugs and unintended data calls.
/songs
, and a template for displaying a list of songs./songs/index
(a change of mind where the songs should display).We'll note this change of mind, and what happens when things are accidentally left behind.
Start with a default site:
ember new mysite
To make things display nicely, add the following CSS to your styles/app.css
file:
body { padding: 10px; } .songs-outlet { border: 2px dashed purple; width: 400px; padding: 10px; } .songs-index-outlet { border: 2px dashed brown; width: 350px; padding: 10px; }
We need a page to display a list of songs. Ideally, we want to browse it from http://localhost:4200/songs
Create the route and template with:
ember g route songs
This will create a route file in /routes/songs.js
and a template file in /templates/songs.hbs
.
It will also add the following to the app/router.js
file:
Router.map(function() { this.route('songs'); });
Edit the /templates/songs.hbs
file to contain:
<h1>Songs from /songs</h1> <p>My favourite songs will be displayed below.</p> <p>Songs outlet start</p> <div class="songs-outlet"> {{outlet}} </div> <p>Songs outlet end</p>
npm run start
your app and browse to http://localhost:4200/songs
.
You should get something that looks like this:
There is a header, some text, and the {{outlet}}
has been make a bit prettier.
Let's make the route trigger a call for data.
Add the following to your /routes/songs.js
file:
import Route from '@ember/routing/route'; export default class SongsRoute extends Route { model() { return fetch('http://localhost:4200/some-song-data'); } }
When the app re-runs, there will be a call out to locate this data.
We don't have Ember Data set up, or a model, or even a real API endpoint to get the data from. And we won't. That's ok. Just seeing that our app is trying to load song data when we visit the /song
page is enough.
It is also enough to illustrate that if song data had returned, we could have looped over it and displayed it in the /templates/songs.hbs
template.
In our scenario, it was decided that the route should be changed from /songs to /songs/index, which to the browser is essentially the same.
So let's create that:
ember g route songs/index
This will create a route file in /routes/songs/index.js
(a songs folder) and a template file in /templates/songs/index.hbs
.
The app/router.js
file will be changed to:
Router.map(function() { this.route('songs', function() {} });
Note the empty function? Child routes are contained in this function, but the index route is special. It's implied so it doesn't get written.
Change the /templates/songs/index.hbs
template to contain:
<h1>Songs Index from /songs/index</h1> <p>My favourite songs will be displayed below.</p> <p>Songs outlet start</p> <div class="songs-index-outlet"> {{outlet}} </div> <p>Songs outlet end</p>
Re-run the app and the following should display:
Well that's interesting, the template of template/songs/index.hbs
is displaying inside the {{outlet}}
of the /templates/songs.hbs
template. We've made a nested route. How did we do that?
The URL of /songs
matches both the route files of /routes/songs.js
and routes/songs/index.js
. But songs.hbs
is treated as the parent, and the songs/index.hbs
is a child and is rendered into the patent's {{outlet}}
. As you can imagine, this is very useful for setting up parent templates (common headers and footers etc) and child content.
But in our scenario, it is unintended.
Let's make it worse!
As part of the refactor, the new route needs to load the same data. So add the same data call to the new route at /routes/songs/index.js
:
import Route from '@ember/routing/route'; export default class SongsIndexRoute extends Route { model() { return fetch('http://localhost:4200/some-song-data'); } }
Re-run the app and you'll now see that both templates have caused a load of data each:
Suppose the developer is now going to remove the old page so that only templates/songs/index.hbs
remains and all should be well. templates/songs.hbs
is removed, the site is re-run and a quick deskcheck ensures that the new page is displaying correctly, and the old parent is gone:
That's pretty good. The PR is checked in, QA passes it and it goes out for the customer.
Some time later it is reported that the page load times are slow. A quick check and it's discovered that data is loading twice on the one route. Of course, the first place to start looking is an older parent route that still might be matching and could be causing data calls.
By removing the forgotten route at /routes/songs.js
, the extra data call is gone.
Hope this tip is useful. Happy ember stoking!