Skip to main content

Angular - Routing and Navigation Part 2

Milestone 1: Getting started with the router

Begin with a simple version of the app that navigates between two empty views.
App in action

Set the 

The router uses the browser's history.pushState for navigation. Thanks to pushState, you can make in-app URL paths look the way you want them to look, e.g. localhost:3000/crisis-center. The in-app URLs can be indistinguishable from server URLs.
Modern HTML5 browsers were the first to support pushState which is why many people refer to these URLs as "HTML5 style" URLs.
HTML5 style navigation is the router default. In the LocationStrategy and browser URL stylesAppendix, learn why HTML5 style is preferred, how to adjust its behavior, and how to switch to the older hash (#) style, if necessary.
You must add a  element to the app's index.html for pushState routing to work. The browser uses the  value to prefix relative URLs when referencing CSS files, scripts, and images.
Add the  element just after the  tag. If the app folder is the application root, as it is for this application, set the href value in index.html exactly as shown here.
src/index.html (base-href)
 href="/">

LIVE EXAMPLE NOTE
A live coding environment like Stackblitz sets the application base address dynamically so you can't specify a fixed address. That's why the example code replaces the  with a script that writes the  tag on the fly.
You only need this trick for the live example, not production code.

Importing from the router library

Begin by importing some symbols from the router library. The Router is in its own @angular/routerpackage. It's not part of the Angular core. The router is an optional service because not all applications need routing and, depending on your requirements, you may need a different routing library.
You teach the router how to navigate by configuring it with routes.

Define routes

A router must be configured with a list of route definitions.
The first configuration defines an array of two routes with simple paths leading to the CrisisListComponentand HeroListComponent.
Each definition translates to a Route object which has two things: a path, the URL path segment for this route; and a component, the component associated with this route.
The router draws upon its registry of definitions when the browser URL changes or when application code tells the router to navigate along a route path.
In simpler terms, you might say this of the first route:
  • When the browser's location URL changes to match the path segment /crisis-center, then the router activates an instance of the CrisisListComponent and displays its view.
  • When the application requests navigation to the path /crisis-center, the router activates an instance of CrisisListComponent, displays its view, and updates the browser's address location and history with the URL for that path.
Here is the first configuration. Pass the array of routes, appRoutes, to the RouterModule.forRoot method. It returns a module, containing the configured Router service provider, plus other providers that the routing library requires. Once the application is bootstrapped, the Router performs the initial navigation based on the current browser URL.
src/app/app.module.ts (first-config)
import { NgModule }             from '@angular/core';
import { BrowserModule }        from '@angular/platform-browser';
import { FormsModule }          from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent }          from './app.component';
import { CrisisListComponent }   from './crisis-list.component';
import { HeroListComponent }     from './hero-list.component';

const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'heroes', component: HeroListComponent },
];

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging="" only="" purposes="" span="">
    )
  ],
  declarations: [
    AppComponent,
    HeroListComponent,
    CrisisListComponent,
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

Adding the configured RouterModule to the AppModule is sufficient for simple route configurations. As the application grows, you'll want to refactor the routing configuration into a separate file and create a Routing Module, a special type of Service Module dedicated to the purpose of routing in feature modules.
Providing the RouterModule in the AppModule makes the Router available everywhere in the application.

The AppComponent shell

The root AppComponent is the application shell. It has a title, a navigation bar with two links, and a router outlet where the router swaps views on and off the page. Here's what you get:
Shell
The corresponding component template looks like this:
src/app/app.component.ts (template)
template: `
  

Angular

Router
a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a> <a routerLink="/heroes" routerLinkActive="active">Heroes</a> <router-outlet></router-outlet> `

RouterOutlet

The RouterOutlet is a directive from the router library that marks the spot in the template where the router should display the views for that outlet.
The router adds the <router-outlet> element to the DOM and subsequently inserts the navigated view element immediately after the <router-outlet>.
Above the outlet, within the anchor tags, you see attribute bindings to the RouterLink directive that look like routerLink="...".
The links in this example each have a string path, the path of a route that you configured earlier. There are no route parameters yet.
You can also add more contextual information to the RouterLink by providing query string parameters or a URL fragment for jumping to different areas on the page. Query string parameters are provided through the [queryParams] binding which takes an object (e.g. { name: 'value' }), while the URL fragment takes a single value bound to the [fragment] input binding.
Learn about the how you can also use the link parameters array in the appendix below.

RouterLinkActive binding

On each anchor tag, you also see property bindings to the RouterLinkActive directive that look like routerLinkActive="...".
The template expression to the right of the equals (=) contains a space-delimited string of CSS classes that the Router will add when this link is active (and remove when the link is inactive). You can also set the RouterLinkActive directive to a string of classes such as [routerLinkActive]="'active fluffy'" or bind it to a component property that returns such a string.
The RouterLinkActive directive toggles css classes for active RouterLinks based on the current RouterState. This cascades down through each level of the route tree, so parent and child router links can be active at the same time. To override this behavior, you can bind to the [routerLinkActiveOptions] input binding with the { exact: true } expression. By using { exact: true }, a given RouterLink will only be active if its URL is an exact match to the current URL.

Router directives

RouterLinkRouterLinkActive and RouterOutlet are directives provided by the Angular RouterModulepackage. They are readily available for you to use in the template.
The current state of app.component.ts looks like this:
src/app/app.component.ts (excerpt)
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    

Angular

Router
a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a> <a routerLink="/heroes" routerLinkActive="active">Heroes</a> <router-outlet></router-outlet> ` }) export class AppComponent { }

Wildcard route

You've created two routes in the app so far, one to /crisis-center and the other to /heroes. Any other URL causes the router to throw an error and crash the app.
Add a wildcard route to intercept invalid URLs and handle them gracefully. A wildcard route has a path consisting of two asterisks. It matches every URL. The router will select this route if it can't match a route earlier in the configuration. A wildcard route can navigate to a custom "404 Not Found" component or redirect to an existing route.
The router selects the route with a first match wins strategy. Wildcard routes are the least specific routes in the route configuration. Be sure it is the last route in the configuration.
To test this feature, add a button with a RouterLink to the HeroListComponent template and set the link to "/sidekicks".
src/app/hero-list.component.ts (excerpt)
import { Component } from '@angular/core';

@Component({
  template: `
    

HEROES

Get your heroes here
`
}) export class HeroListComponent { }

The application will fail if the user clicks that button because you haven't defined a "/sidekicks" route yet.
Instead of adding the "/sidekicks" route, define a wildcard route instead and have it navigate to a simple PageNotFoundComponent.
src/app/app.module.ts (wildcard)
{ path: '**', component: PageNotFoundComponent }

Create the PageNotFoundComponent to display when users visit invalid URLs.
src/app/not-found.component.ts (404 component)
import { Component } from '@angular/core';

@Component({
  template: '

Page not found

'
}) export class PageNotFoundComponent {}

As with the other components, add the PageNotFoundComponent to the AppModule declarations.
Now when the user visits /sidekicks, or any other invalid URL, the browser displays "Page not found". The browser address bar continues to point to the invalid URL.

The default route to heroes

When the application launches, the initial URL in the browser bar is something like:
localhost:3000

That doesn't match any of the concrete configured routes which means the router falls through to the wildcard route and displays the PageNotFoundComponent.
The application needs a default route to a valid page. The default page for this app is the list of heroes. The app should navigate there as if the user clicked the "Heroes" link or pasted localhost:3000/heroes into the address bar.

Redirecting routes

The preferred solution is to add a redirect route that translates the initial relative URL ('') to the desired default path (/heroes). The browser address bar shows .../heroes as if you'd navigated there directly.
Add the default route somewhere above the wildcard route. It's just above the wildcard route in the following excerpt showing the complete appRoutes for this milestone.
src/app/app-routing.module.ts (appRoutes)
const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'heroes',        component: HeroListComponent },
  { path: '',   redirectTo: '/heroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

A redirect route requires a pathMatch property to tell the router how to match a URL to the path of a route. The router throws an error if you don't. In this app, the router should select the route to the HeroListComponent only when the entire URL matches '', so set the pathMatch value to 'full'.
Technically, pathMatch = 'full' results in a route hit when the remaining, unmatched segments of the URL match ''. In this example, the redirect is in a top level route so the remaining URL and the entire URL are the same thing.
The other possible pathMatch value is 'prefix' which tells the router to match the redirect route when the remaining URL begins with the redirect route's prefix path.
Don't do that here. If the pathMatch value were 'prefix'every URL would match ''.
Try setting it to 'prefix' then click the Go to sidekicks button. Remember that's a bad URL and you should see the "Page not found" page. Instead, you're still on the "Heroes" page. Enter a bad URL in the browser address bar. You're instantly re-routed to /heroesEvery URL, good or bad, that falls through to this route definition will be a match.
The default route should redirect to the HeroListComponent only when the entire url is ''. Remember to restore the redirect to pathMatch = 'full'.
Learn more in Victor Savkin's post on redirects.

Basics wrap up

You've got a very basic navigating app, one that can switch between two views when the user clicks a link.
You've learned how to do the following:
  • Load the router library.
  • Add a nav bar to the shell template with anchor tags, routerLink and routerLinkActive directives.
  • Add a router-outlet to the shell template where views will be displayed.
  • Configure the router module with RouterModule.forRoot.
  • Set the router to compose HTML5 browser URLs.
  • handle invalid routes with a wildcard route.
  • navigate to the default route when the app launches with an empty path.
The rest of the starter app is mundane, with little interest from a router perspective. Here are the details for readers inclined to build the sample through to this milestone.
The starter app's structure looks like this:
router-sample
src
app
app.component.ts
app.module.ts
crisis-list.component.ts
hero-list.component.ts
not-found.component.ts
main.ts
index.html
styles.css
tsconfig.json
node_modules ...
package.json
Here are the files discussed in this milestone.

  1. import { Component } from '@angular/core';
  2.  
  3. @Component({
  4. selector: 'app-root',
  5. template: `
  6. Angular

    Router
  • <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
  • <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
  • <router-outlet></router-outlet>
  • `
  • })
  • export class AppComponent { }
  • Comments