====== Ember.js Routes and Troubleshooting ====== {{ :ember:ember-title-card.jpg?nolink&300|}}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 ===== - To create a route to ''/songs'', and a template for displaying a list of songs. - Creating a fake data call to bring in a list of songs. - Creating a new route to ''/songs/index'' (a change of mind where the songs should display). - Removing the old page. - 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:

Songs from /songs

My favourite songs will be displayed below.

Songs outlet start

{{outlet}}

Songs outlet end

''npm run start'' your app and browse to ''http://localhost:4200/songs''. You should get something that looks like this: {{ :ember:songs-template-browser.png?nolink |}} 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. {{ :ember:songs-route-data-call.png?nolink |}} 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:

Songs Index from /songs/index

My favourite songs will be displayed below.

Songs outlet start

{{outlet}}

Songs outlet end

Re-run the app and the following should display: {{ :ember:songs-template-songsindex-template-browser.png?nolink |}} 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: {{ :ember:songs-and-songsindex-route-data-calls.png?nolink |}} ===== 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: {{ :ember:songsindex-template-browser.png?nolink |}} 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 ===== * Thanks to Jeremy.S for checking over this article. * A great article that covers similar material on routes is at: [[Getting Started with Ember Octane: Building a Blog | https://emberigniter.com/getting-started-ember-octane-tutorial/]] by Frank Treacy. Well worth a read.