Angular 5 – Routing (Practical Guide)

Angular provides an easy way to create and work with components , In a single page application(SPA) it is essential to work with multiple views/screens, navigate and communicate between them. Angular provides router service to manage this in a very easy way.

We can create a routing module while creating a new application or add it later

To create a new Angular application with routing support:

# ng new RouteApp --routing

Assume we want to display a menu bar with the following titles:

First we will add bootstrap support to our app – see this page for instructions

Add 4 components (books, customers,loans, welcome). Add component for Page Not Found

Add bootstrap Navbar to the app.component.html file , also add <router-outlet> directive to specify the place holder for the routed components:

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <div class='container'>
    <ul class="nav navbar-nav">
      <li class='nav-item'>
        <a class='nav-link' routerLink="/">Home</a>
      </li>
      <li class='nav-item'>
        <a class='nav-link' routerLink="/books">Books</a>
      </li>
      <li class='nav-item'>
        <a class='nav-link' routerLink="/customers">Customers</a>
      </li>
      <li class='nav-item'>
        <a class='nav-link' routerLink="/loans">Loans</a>
      </li>
    </ul>
  </div>
</nav>
<router-outlet></router-outlet>

Now create the routing table in app.routing.ts file:

import {ModuleWithProviders} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {BooksComponent} from './books.component';
import {CustomersComponent} from './customers.component';
import {LoansComponent} from './loans.component';
import {WelcomeComponent} from './welcome.component';
import {BookDetailsComponent} from './book-details.component';
import {CustDetailsComponent} from './cust-details.component';
import {CustEditComponent} from './cust-edit.component';
import {BookGuard } from './book.guard';
import {PageNotFoundComponent} from "./page-not-found.component";


const appRoutes : Routes =
  [
    {
      path: '',
      component: WelcomeComponent
    },
    {
      path: 'books',
      component: BooksComponent
    },
    {
      path: 'customers',
      component: CustomersComponent,
    },
    {
      path: 'loans',
      component: LoansComponent,

    },
    {
      path: '**',
      component: PageNotFoundComponent
    }
  ];

export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);

Each entry specify the address bar path and the component to render into the router-outlet

the path ‘**’ specify any non valid route and navigate to PageNotFound component

Add the routing object to the module imports (app.module.ts)

@NgModule({
  declarations: [
    AppComponent,
    BooksComponent,
    CustomersComponent,
    ...
    PageNotFoundComponent
  ],
  imports: [
    BrowserModule,
    routing
    ...
  ],

Adding parameters

You can add parameters to the route to send extra info – for example if you want Master-Details view:

While clicking details we want to navigate to a page with full book details.

Add a new component: BookDetails

Update the routing table (Add new object):

{
      path: 'book/:id',
      component: BookDetailsComponent,
}

The parameter name is “id” , and the link should look like http://server.com/book/100

You can add more parameters this way for example to add the price:  path: ‘book/:id/:price’

To add a link for book details use [routerLink] property binding. For example assume we have a list of Books objects we can create the table with the links:

import { Component, OnInit ,OnDestroy} from '@angular/core';
import {Router, ActivatedRoute} from '@angular/router';
import {Subscription} from 'rxjs/Rx';

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

    <table align="center" border='1' width='80%' *ngIf='books && books.length > 0'>
        <tbody>
            <tr *ngFor='let b of books'>
                <td> {{b.Id}} </td>
                <td> {{b.Name }} </td>
                <td> {{b.Price}} </td>
                <td> {{b.Rate}} </td>
                <td> <a [routerLink]="['/book',b.Id]">Details</a> </td>
            </tr>
        </tbody> 
    </table>
    
  `,
  styles: []
})
export class BooksComponent implements OnDestroy{

  books:Book [] = [
    {
      "Id": 100,
      "Name": "book1",
      "Price": 123,
      "Rate": 3
    },
    {
     ....
    },
    {
      "Id": 400,
      "Name": "book4",
      "Price": 250,
      "Rate": 4
    },

  ];

}

export class Book {
  Id:number;
  Name:string;
  Price:number;
  Rate:number;
}
Navigate programatically

You can change the routing link to any event and use the router service to navigate:

import { Component, OnInit ,OnDestroy} from '@angular/core';
import {Router, ActivatedRoute} from '@angular/router';
import {Subscription} from 'rxjs/Rx';

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

    <table align="center" border='1' width='80%' *ngIf='books && books.length > 0'>
        <tbody>
            <tr *ngFor='let b of books'>
                <td> {{b.Id}} </td>
                <td> {{b.Name }} </td>
                ...............
                <td> <button (click)="onclick(b.Id)">click</button>
            </tr>
        </tbody> 
    </table>
    
  `,
  styles: []
})
export class BooksComponent implements OnDestroy{

  constructor(private _router:Router){}

  onclick(i){
    this._router.navigate(['book' , i ]);
  }

  books:Book [] = [
    {
....

When you navigate you can add query parameters:

this._router.navigate(['books'],{queryParams:{'val': this.num}});  // navigate to http://server/books?val=100

And fragment

this._router.navigate(['books'],{fragment:'order'});    // navigate to http://server/books#order

On the BookDetails component you can read the parameters using ActivatedRoute service:

export class BookDetailsComponent implements OnInit {
  id:number;
  price:number;
  constructor(private _activeRoute:ActivatedRoute) { }

  ngOnInit() {
    this.id = this._activeRoute.snapshot.params['id'];
    this.price = this._activeRoute.snapshot.params['price'];
  }

}
The ActivatedRoute Service

The ActivatedRoute service lets you read the routing parameters, the query parameters and the fragment name

Another useful feature is to  subscribe to changes in query params or fragments:

_activeRoute.queryParams.subscribe(param => this.qnum = param['val']);

_activeRoute.fragment.subscribe(
      fragment => console.log(fragment)
    );

Creating Child routes

Sometimes we want a child route for example we want another menu in Customer component with some titles: edit, new, details, etc.

You need to create the components and update the route table:

    {
      path: 'customers',
      component: CustomersComponent,
      children: [
        {
          path: 'edit',
          component: CustEditComponent
        },
        {
          path: 'details',
          component: CustDetailsComponent
        }
      ]
    },

Also, Add <router-outlet> directive to the parent component (customers)

Navigation Guards

For better UX we need to disable a route sometimes (for example the customer didn’t fill the form yet). We can write code to guard the navigation:

To create a guard we can use Angular CLI:

# ng g guard bookGuard

We need to write a class and implement CanActivate, CanDeactivate, and more (see docs)

For example we navigate to book only if the price is greater than 200 – assume the navigation has 2 parameters ‘book/:id/:price’

@Injectable()
export class BookGuard implements CanActivate {

  constructor() { }

  canActivate(_route: ActivatedRouteSnapshot): boolean {
    let price = +_route.url[2].path;
    if (price > 200)
      return true;
    return false;
  }

}

we need to add the class to the routing table:

{
      path: 'book/:id/:price',
      component: BookDetailsComponent,
      canActivate: [BookGuard]
},

And because its a service we need to add it to the providers array of the application module (app.module.ts)

providers: [BookGuard]

We can also run the guard code asynchronously – the canActivate function returns boolean or Promise or Observable:

 

@Injectable()
export class SimpGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    

  }
}

 

 

 

Tagged