Navigation

Learning Objectives

How to navigate between the different routes in an Angular application.

Navigating by hardcoded URLS

We could simply hardcode the URLs in the href anchor attributes on our navigation header, like so:

<nav class="navbar navbar-light bg-faded">
  <a class="navbar-brand" href="/#/">iTunes Search App</a>
  <ul class="nav navbar-nav">
    <li class="nav-item active">
      <a class="nav-link" href="/#/">Home</a> (1)
    </li>
    <li class="nav-item">
      <a class="nav-link" href="/#/search">Search</a>
    </li>
  </ul>
</nav>
1 We simply add a standard href with a value of /#/

Important

This works for our example because we are using something called a HashLocationStrategy (more on that later) but hardcoding like this doesn’t work with the other location strategy available in Angular, PathLocationStrategy.

If we were using that strategy clicking one of those links would result in the browser trying to request the whole page again from the server which defeats the purpose of trying to create an SPA.

Navigating programmatically via the router

In Angular we can also programmatically navigate via a Router service we inject into our component, like so:

import {Router} from "@angular/router";
.
.
.
@Component({
  selector: 'app-header',
  template: `
<nav class="navbar navbar-light bg-faded">
  <a class="navbar-brand" (click)="goHome()">iTunes Search App</a> (1)
  <ul class="nav navbar-nav">
    <li class="nav-item">
      <a class="nav-link" (click)="goHome()">Home</a> (1)
    </li>
    <li class="nav-item">
      <a class="nav-link" (click)="goSearch()">Search</a> (1)
    </li>
  </ul>
</nav>
 `
})
class HeaderComponent {
  constructor(private router: Router) {} (2)

  goHome() {
    this.router.navigate(['']); (3)
  }

  goSearch() {
    this.router.navigate(['search']); (3)
  }
}
1 We added click handlers to each anchor tag to call functions on our HeaderComponent.
2 We inject and store a reference to the Router into our HeaderComponent.
3 We then call the navigate function on the router to navigate between the different URLs.

The value we pass into the navigate function might look a bit strange, we call it a link params array and it equivalent to the URL split by the / character into an array.

Important

We don’t have to pass in the # character in the parameters to the navigate function, it automatically adds them in if we are using the HashLocationStrategy.

We can demonstrate by changing our search route from

{path: 'search', component: SearchComponent},

to

{path: 'search/foo/moo', component: SearchComponent},

Then to navigate to our search page we pass into the navigate function an link params array like so:

this.router.navigate(['search', 'foo', 'moo']);

Navigating via a link params array has one big advantage in that parts of the URL can be variables, like so:

let part = "foo";
this.router.navigate(['search', part, 'moo']);

Note

This becomes a lot more useful when we start dealing with parametrised routes later on in this section.

We can also control navigation by using the routerLink directive in the template itself, like so:

<nav class="navbar navbar-light bg-faded">
  <a class="navbar-brand" [routerLink]="['home']">iTunes Search App</a>
  <ul class="nav navbar-nav">
    <li class="nav-item active">
      <a class="nav-link"  [routerLink]="['home']">Home</a>
    </li>
    <li class="nav-item">
      <a class="nav-link"  [routerLink]="['search']">Search</a>
    </li>
  </ul>
</nav>

The routerLink directive takes as input the same link params array format that the router.navigate(…​) function takes.

routerLinkActive

An important feature of any navigation component is giving the user feedback about which menu item they are currently viewing. Another way to describe this is giving the user feedback about which route is currently active.

With the twitter bootstrap navigation styles we give this feedback by adding a class of active to the parent element to the anchor tag, like so:

<li class="nav-item active"> (1)
  <a class="nav-link" [routerLink]="['home']">Home</a>
</li>
1 Adding active to the parent element highlights the anchor tag.

To help in adding and removing classes depending on the currently active route Angular provides another directive called routerLinkActive.

routerLinkActive directive is associated with a route through a routerLink directive.

It takes as input an array of classes which it will add to the element it’s attached to if it’s route is currently active, like so:

<a class="nav-link"
   [routerLink]="['home']"
   [routerLinkActive]="['active']">
   Home
</a>

The above will add a class of active to the anchor tag if we are currently viewing the home route.

However this isn’t so useful for us in twitter bootstrap since we need the active class set on the parent li element.

But that’s fine, the routerLinkActive directive can be set on a parent element of the routerLink directive and it will still associate itself with the route, like so:

<nav class="navbar navbar-light bg-faded">
  <a class="navbar-brand"
     [routerLink]="['home']">iTunes Search App
  </a>
  <ul class="nav navbar-nav">
    <li class="nav-item"
        [routerLinkActive]="['active']">
      <a class="nav-link"
         [routerLink]="['home']">Home
      </a>
    </li>
    <li class="nav-item"
        [routerLinkActive]="['active']">
      <a class="nav-link"
         [routerLink]="['search']">Search
      </a>
    </li>
  </ul>
</nav>
active menu navigation

Summary

In this lecture we’ve shown how we can navigate between routes in Angular programmatically via the router and via the template by using the routerLink directive.

We’ve also explained that both these methods require a _link params array+ to be passed in order to function.

And finally we’ve shown how to add some user feedback as to the currently active route by using the routerLinkActive directive.

In the next lecture we’ll explain how to add variable parameters to routes via parametrised routes.

Listing

Listing 1. script.ts
import {NgModule, Component, Injectable} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {JsonpModule, Jsonp, Response} from '@angular/http';
import {ReactiveFormsModule, FormControl, FormsModule} from '@angular/forms';
import {Routes, RouterModule, Router} from "@angular/router";
import {Observable} from 'rxjs';
import 'rxjs/add/operator/toPromise';

class SearchItem {
  constructor(public name: string,
              public artist: string,
              public link: string,
              public thumbnail: string,
              public artistId: string) {
  }
}

@Injectable()
class SearchService {
  apiRoot: string = 'https://itunes.apple.com/search';
  results: SearchItem[];

  constructor(private jsonp: Jsonp) {
    this.results = [];
  }

  search(term: string) {
    return new Promise((resolve, reject) => {
      this.results = [];
      let apiURL = `${this.apiRoot}?term=${term}&media=music&limit=20&callback=JSONP_CALLBACK`;
      this.jsonp.request(apiURL)
          .toPromise()
          .then(
              res => { // Success
                this.results = res.json().results.map(item => {
                  return new SearchItem(
                      item.trackName,
                      item.artistName,
                      item.trackViewUrl,
                      item.artworkUrl30,
                      item.artistId
                  );
                });
                resolve();
              },
              msg => { // Error
                reject(msg);
              }
          );
    });
  }
}


@Component({
  selector: 'app-search',
  template: `<form class="form-inline">
  <div class="form-group">
    <input type="search"
           class="form-control"
           placeholder="Enter search string"
           #search>
  </div>
  <button type="button"
          class="btn btn-primary"
          (click)="doSearch(search.value)">
    Search
  </button>
</form>

<hr />

<div class="text-center">
  <p class="lead"
     *ngIf="loading">Loading...</p>
</div>

<div class="list-group">
  <a href="#"
     class="list-group-item list-group-item-action"
     *ngFor="let track of itunes.results">
    <img src="{{track.thumbnail}}">
    {{ track.name }} <span class="text-muted">by</span> {{ track.artist }}
  </a>
</div>
 `
})
class SearchComponent {
  private loading: boolean = false;

  constructor(private itunes: SearchService) {
  }

  doSearch(term: string) {
    this.loading = true;
    this.itunes.search(term).then(_ => this.loading = false)
  }
}

@Component({
  selector: 'app-home',
  template: `
<div class="jumbotron">
  <h1 class="display-3">iTunes Search App</h1>
</div>
 `
})
class HomeComponent {
}

@Component({
  selector: 'app-header',
  template: `<nav class="navbar navbar-light bg-faded">
  <a class="navbar-brand"
     [routerLink]="['home']">iTunes Search App
  </a>
  <ul class="nav navbar-nav">
    <li class="nav-item"
        [routerLinkActive]="['active']">
      <a class="nav-link"
         [routerLink]="['home']">Home
      </a>
    </li>
    <li class="nav-item"
        [routerLinkActive]="['active']">
      <a class="nav-link"
         [routerLink]="['search']">Search
      </a>
    </li>
  </ul>
</nav>
 `
})
class HeaderComponent {
  constructor(private router: Router) {
  }

  goHome() {
    this.router.navigate(['']);
  }

  goSearch() {
    this.router.navigate(['search']);
  }
}

@Component({
  selector: 'app',
  template: `
	<app-header></app-header>
	<div class="m-t-1">
    <router-outlet></router-outlet>
  </div>
 `
})
class AppComponent {
}

const routes: Routes = [
  {path: '', redirectTo: 'home', pathMatch: 'full'},
  {path: 'find', redirectTo: 'search'},
  {path: 'home', component: HomeComponent},
  {path: 'search', component: SearchComponent},
  {path: '**', component: HomeComponent}
];


@NgModule({
  imports: [
    BrowserModule,
    ReactiveFormsModule,
    FormsModule,
    JsonpModule,
    RouterModule.forRoot(routes, {useHash: true})
  ],
  declarations: [
    AppComponent,
    SearchComponent,
    HomeComponent,
    HeaderComponent
  ],
  bootstrap: [AppComponent],
  providers: [SearchService]
})
class AppModule {
}

platformBrowserDynamic().bootstrapModule(AppModule);

Learn Angular 5 For FREE

I've released my 700 page Kick Starter funded Angular 5 book for FREE