Check out a free preview of the full Ember Octane Fundamentals course

The "Forwarding Routes" Lesson is part of the full, Ember Octane Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:

Mike discusses index pages and how they relate to forwarding routes. Then, a default is set for the teams and channels routes in the cases that there is and is no data available.


Transcript from the "Forwarding Routes" Lesson

>> Mike: The next thing we're gonna do, is address these situations where we had incomplete URLs or part of our chat UI was blank. Remember, if we didn’t specify a particular team, or a channel within that team, we kind of got half of our UI and then a blank area.

So we're gonna solve this with forwarding routes. These are similar to the way our login and our teams before model hooks, where they'd kick the user out or allow them to go through. We'll create some index routes, I call them index routes cuz index JS will be their file name, just placed it in various spots within our routing hierarchy.

And they'll take care of handling situations where you've kind of, with your URL you've addressed a folder instead of a file, right, like teams slash. And there's a deeper level of nesting we wanna handle. We can use Ember CLI to generate these routes for us, and we're just gonna handle them one by one.

And eventually, we'll be able to get back to a situation where you could go /teams and you'll end up, say, picking the first team and the first channel in that team. And ending up like no matter what you omit, you kinda land somewhere. So let's get started. Ember generate, and I'll tell you now, you can just say ember g instead of ember generate.

And I use this enough that I'm having to force myself to generate in this workshop ember g route teams/index. Before I hit Enter, you can hit enter right away, I just want to open this file up and this may get reformatted, but let's just remember there is teams, team, channel.

>> Mike: And nothing has changed in this file. Anytime you have a deeper level of nesting in your routing hierarchy, right, like teams has children, has sub routes inside of it. Anytime you have that, you kind of get an implicit index route for free. And if you remember, when we were visiting those incomplete your URLs, we were seeing blank stuff on the screen but I didn't point out error messages.

We didn't have error messages. Those are valid URLs for this application. We just hadn't handled them yet. So, just like when you visit, you implicitly are requesting index.html, here if you go teams/ or just teams, you are implicitly requesting teams/index, right. That's why I call it like a folder level route.

So let's get started. And by the way, now that we're using a real API, you'll start to see API calls down here. So that's completely normal. Just more feedback for us. So we've got teams, and we're just gonna go /teams. Now, no error down here as promised. And I'm gonna go to teams/index.

So it's app, routes, teams, index.
>> Mike: And in here, we want to in the event, we have more than zero teams. We have at least one team. I just wanna forward the user to that team. So as before, we will use the before model hook, which gets a transition.

>> Mike: And just in case the parents' implementation is something promisey, we could be extra diligent here.
>> Mike: And now, we're going to get our parent routes model, right. We're in teams/index so we can of course, reach up to our parents, which is teams and grab that routes model.

>> Mike: This model for teams.
>> Mike: Now, in the event that we have at least one team, we're gonna send the user to the 0th team.
>> Mike: This, I think replaceWith is probably appropriate here. Again, we're gonna just have to test it, and we're gonna just link to where we have to pass things to fill in those dynamic segments.

Fill in the blanks in our parametrized URL, teams[0].id. So there we go. If we try to go to /teams, we end up with going to LinkedIn. If we wanted to pick a different default to send users to, we could send them to the last team. So this URL is for LinkedIn specifically.

But if I go back to /teams, I didn't mean to open up a new tab but there you go. We're at the last one JavaScript. So we'll keep it at the first one. But I just want you to understand that we can make decisions about what our default is.

Now, what about if there are no teams? We can show users something. We have a template and for now, just to handle this case without altering too much. I'm gonna just remove this redirect,
>> Mike: So that we're always in this case where teams.length is zero, right? If we did this, we are kind of in the else case right now.

I mean not really, but we're handling that. So now we can go to our templates,
>> Mike: Teams/index.hbs and we'll put here, sorry, no teams found.
>> Mike: And there it's showing up on the screen. We could format that and make it pretty, but it's not really gonna help us learn more about Ember.

So, that's what's going to happen if we don't catch a user and transition them somewhere else. They'll land here, in the event that that team’s array is empty. So now, we can uncomment this,
>> Mike: And there you go, so they’ll never end up with that index.hbs if we transition them somewhere else, they’ll just never end up there.

The existing attempt to land on that URL, that gets aborted and then they're sent to another place, sent to Let's do the same thing, one more layer down for channels. I'm gonna copy this cuz it's gonna be very similar teams/team. Sorry, we got to generate that out, teams/team/index.

So ember g route teams/team/index, this would be like if you did. You wanted to look at the LinkedIn team, no channel specified.
>> Mike: And let's look at that file that was created, teams/team/index.js. Paste this in here, and we want to get the model for the team, and we'll rename this variable.

In fact, we don't really care about the team, we just care about this channels property.
>> Mike: Right, remember how we enter sidebar, we did @team.channels, we iterated over that. So if the channel's length is greater than zero,
>> Mike: We'll want to send the user to this route. Turns out we're actually going to need the ID, my mistake here.

We'll need the team's ID. And we'll pass that as the first part of the URL to fill in cuz it's going to look kind of like this, teams/teamId/channelId. And so, channelId, that's gonna be the first channel in the array and the teamId we're extracting out up here.
>> Mike: And there we go.

So I could do /teams and we'll end up in the first channel of the first team, right? We could make it the second channel of the first team just by tweaking it a little bit. Sorry, the second because we count from zero. And there we go, so now we're in job hunting instead of recruiting.

What we've effectively done now is we have made it so all of these sort of partial URLs, they fall back onto a reasonable default.
>> Mike: So we could alter, I know at least one test is failing now. Remember our login tests where we expect to be at a particular URL.

Now with this forwarding that we've added, we're gonna have to update that. But this is the way we would want this app. If you're trying to sell this, you don't want to have a bunch of URLs that don't work. So, let's take a look at the tests.
>> Mike: Interesting, they pass.

>> Mike: I’m a little puzzled about why they passed.
>> Mike: Let me try this logout test, I’m going to just try to walk through it manually.
>> Mike: You know what, we only have our logout test.
>> Mike: This makes sense, okay? Past Mike did present Mike a little favor here, I forgot, cuz I improve this on the fly.

I forgot that we instead of asserting that attempting to visit this URL, we landed on exactly that URL. A lot of our acceptance tests look like that, right? They look like assert.equals('/teams/linkedin').
>> Mike: Sorry, current wrap, currentURL.
>> Mike: So this is one mechanism of assertion. And if we had done this, the test would have failed, right?

As a result of the forwarding we just added. We send the user to a particular channel but we made our assertion a little bit more tolerant of other kinds of things and so yeah, it still starts with /teams. So we happen to be fine.
>> Mike: So I'll get rid of this, leave the test as is.

>> Speaker 2: Is there any meaningful difference between the paramsFor and modelFor?
>> Mike: Good question. The difference between them is the following. Well, the commonality between them is you're specifying a route name. And you're from a child route reaching up to a parent. The difference is, paramsFor will give you whatever that parent route was passed as an argument to its model hook.

ModelFor will give you whatever that model hook returned or resolved to. So it's the difference between getting the model versus getting the dynamic segments, but they're very closely related.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now