September 2018

Volume 33 Number 9

[The Working Programmer]

How to be MEAN: Routing Angular

By Ted Neward | September 2018

Ted NewardWelcome back again, MEANers.

Up to this point, despite all we’ve accomplished, everything has essentially been done entirely within the scope of one “page.” While this does make sense for some single-page applications (SPAs), users of Web apps, even the most sophisticated Web apps (or the most sophisticated users) generally follow some of the established principles of the Web, like accessibility via a URL. That is to say, I should be able to “jump” to certain parts of the application by simply entering the appropriate URL into the browser, or bookmark a page while I’m on it, and so on. The ability to navigate “inside” the application is one of the hallmarks that distinguishes the Web app from the desktop or mobile app, and it’s an important feature that should be supported.

In an older, more traditional Web application, this is handled by the very nature of the traditional ASP.NET (or Java servlets or Ruby-on-Rails or PHP or …) application: Everything is a separate and distinct Web page that’s manufactured on the server, then sent to the client for rendering. Within an SPA, however, most (if not all) of the rendering is done entirely client-side, and you only go back to the server when you need data or have to invoke some behavior that needs to remain tucked away on the server (such as modifying data in a shared database, or perhaps invoking a separate Web service hiding away behind the firewall on the user’s behalf).

Thus, within most SPA frameworks—like Angular—a different mechanism is required in order to provide the kind of “scoping” or “segmenting” that page boundaries provide. In essence, you need some kind of tool or mechanism to change “pages” within the SPA, essentially ripping out whatever components are currently being displayed and replacing them with a different set of components, so that to the user, it looks like you changed pages, but without having to do the HTTP round-trip that navigating to a new page normally entails. Within Angular, that mechanism is called “routing,” and it’s the responsibility of the Angular Router. (And you, of course.)

Routing Fundamentals

To better grasp how routing works, let’s assume a standard master-­detail style of application: The first page will display a list of items in which I’m interested (such as speakers at a conference) by some sort of summarization (such as by last name and first name). When a user wants to “drill down” into a more detailed display of a single item, they’ll click on that item in the list and I’ll bring up a more detailed view. In Angular terms, this means I want to have two components to work with: a SpeakerListComponent, to display the speakers by name, and a SpeakerComponent, to display the full details of that speaker. This is pretty standard master-­detail stuff, and more importantly, it’s the central staple of countless business applications. Naturally, there’re other (perhaps better) ways of building a business UI, but this serves to get the core point across—I need to figure out how to route from the SpeakerListComponent to a given SpeakerComponent, and pass the selected speaker in while I’m at it.

A natural place to start with routing is with the collection of routes themselves. It’s typical to want the homepage of the app to be the “master” view (the list of speakers), so let’s start with that. As routing is usually something that’s application-wide, or at least module-wide, typically routes are defined in the app.module.ts file, by constructing a Routes object imported from the “@angular/­routing” module:

const appRoutes: Routes = [
  { path: 'speakers', component: SpeakerlistComponent }
];

Note that the routes look essentially the same as they’d look in other Web technologies, like ASP.NET MVC or even Ruby-on-Rails. Routes are, at heart, a mapping of a URL path (without the leading slash) to a component that should be displayed. So, in this particular case, when a user navigates to “https://localhost:4200/speakers,” they’ll be rewarded with the list of speakers at the conference.

Come to think of it, I’d like the “/” path to redirect to the “speakers” route, so that coming to the “homepage” would automatically redirect to the list of speakers, so let’s add that:

const appRoutes: Routes = [
  { path: 'speakers', component: SpeakerlistComponent },
  { path: '', redirectTo: '/speakers', pathMatch: 'full' }
];

Of course, the other thing that’s often needed is some kind of “Oops!” page to be shown if the user goes to a URL that doesn’t exist on your site, so you also need a “wildcard” route that will display the PageNotFoundComponent you’ll have the intern build over the summer:

const appRoutes: Routes = [
  { path: 'speakers', component: SpeakerlistComponent },
  { path: '', redirectTo: '/speakers', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

Note that the wildcard route doesn’t really look any different, aside from the “**” path, but it’s deliberately put at the end of the Routes table. This is by design, because routes are evaluated top-down, with the first match “winning.” Thus, if the wildcard route were at the top, you’d always be showing off the intern’s summer work, no matter whether the user navigated to a correct route or not.

Last, I need to set up a route to display an individual speaker, and the typical way to do that is to give each speaker a page/route that makes use of some sort of unique identifier associated with them, something like “/speaker/1” for the speaker with ID 1. Setting up that route will look familiar to anyone with any familiarity with ASP.NET MVC or Rails, again, in that you use a colon-prefixed parameter name as a placeholder for the actual value passed in:

const appRoutes: Routes = [
  { path: 'speaker/:id', component: SpeakerComponent },
  { path: 'speakers', component: SpeakerlistComponent },
  { path: '', redirectTo: '/speakers', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

The only real question left on that subject is how the SpeakerComponent obtains the “id” parameter; therein lies an interesting tale.

ActivatedRoutes

When routing kicks in and brings a component to the screen, an ActivatedRoute object contains information about the route used, including the parameters to the route (such as the “:id” used earlier). Like most things in Angular, an ActivatedRoute is an injectable object, so you can ask for it in the constructor of the SpeakerComponent to know which speaker to display, as shown in Figure 1.

Figure 1 Asking for an ActivatedRoute in the Constructor

@Component({
  selector: 'app-speaker',
  templateUrl: './speaker.component.html',
  styleUrls: ['./speaker.component.css']
})
export class SpeakerComponent implements OnInit {
  @Input() speaker : Speaker
  constructor(private speakerService: SpeakerService,
    private route: ActivatedRoute) {
  }
  ngOnInit() {
    const speakerId = this.route.snapshot.params['id'];
    this.speaker = this.speakerService.getSpeakerById(speakerId);
  }
}

The ActivatedRoute is heavily wrapped in observable entities, which is a bit more than I want to get into here, so suffice it to say that obtaining a “snapshot” of the route is the easiest way to get hold of the parameters passed in; from there I ask for the “id” parameter, and the “1” in “/speaker/1” is handed back to me for use with the SpeakerService.

The ActivatedRoute can operate via of a number of different URL parts, by the way, in case you were wondering if the route can be mapped by URL parts or fragments or even query parameters. The short answer is yes, the ActivatedRoute can give you access to any part of the URL that you might want to parameterize, and the long answer is, of course, check the Angular documentation for all the details. In fact, routes can actually incorporate arbitrary data as part of the route, and resolve that data just prior to the route’s activation, but that’s a little far afield of what I have room to discuss this time around.

What’s missing still? Two parts: choosing where in the UI the router gets to do its magic, and linking between the master list and the detail components. Both happen in template files, rather than the TypeScript code.

First, the most common place for the router to appear is usually in the root of the application itself; that is to say, the application component will most often define where the router’s various components should appear. In fact, usually the application component will have the router’s “space” surrounded by a header component above it, a footer component below it and so on. The router’s “space” is defined by the “router-outlet” tag, and it’s almost always empty—­inside of it is where the routed components will appear, so my app.component.html will look like this:

<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
</div>
<h2>Welcome to our conference</h2>
<router-outlet></router-outlet>
<h6>No speakers are actually going to appear, so...</h6>

Certainly, the conference Web site could do with a makeover, but I’ll leave that for a Web designer. The key here is that the “router-­outlet” tag pair will be replaced by either the SpeakerlistComponent or the SpeakerComponent, depending on which route is used.

The other thing that must be done is to put links in the Speaker­listComponent, so users can click on the speaker’s name and be taken to their details page. The easiest way to do this is to simply provide standard-issue hrefs in anchor tags in the Speakerlist­Component’s template, like so:

<div>
  <div *ngFor="let speaker of speakers">
    <a href="/speaker/{{speaker.id}}">
      {{speaker.firstName}} {{speaker.lastName}}</a>
  </div>
</div>

And with that, we have a basic master-detail application.

Wrapping Up

There’s a great deal more to discover about routing—it’s a far more complex subject than I can completely cover here. For example, you can also specify an arbitrary (static) “data” parameter when defining the routes collection, which can then be picked up in the activated route object, or you can define what Angular calls a “resolve guard,” which can be used to do certain processing (such as retrieve the data for the selected speaker) while the UI is still being constructed. As always, the Angular documentation has copious details for those who are interested to learn more.

For now, however, we have our master-detail approach working, and it’s time for us to part ways for the month. In the next episode, we’ll talk about how to use Angular’s support for automated tests to test this sucker. In the meantime, happy coding!


Ted Neward is a Seattle-based polytechnology consultant, speaker, and mentor, currently working as the director of Engineering and Developer Relations at Smartsheet.com. He’s written a ton of articles, authored and co-authored a dozen books, and speaks all over the world. Reach him at ted@tedneward.com or read his blog at blogs.tedneward.com.

Thanks to the following technical expert: Garvice Eakins (Smartsheet.com)


Discuss this article in the MSDN Magazine forum