User Tools

Site Tools


ember-routes-and-troubleshooting

Ember.js Routes and Troubleshooting

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.

Goals for this article

  1. To create a route to /songs, and a template for displaying a list of songs.
  2. Creating a fake data call to bring in a list of songs.
  3. Creating a new route to /songs/index (a change of mind where the songs should display).
  4. Removing the old page.
  5. Fixing the problem.

We'll note this change of mind, and what happens when things are accidentally left behind.

Creating a new site

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;
}

Our first route and template

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.

Make a call for song data

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.

The refactor

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:

Removing the old page

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!

Acknowledgements

ember-routes-and-troubleshooting.txt ยท Last modified: 2022/06/27 22:35 by sausage