Overview Of Routing In Angular 2 - Part Two

This article is in continuation of our previous article on Routing in Angular 2. If you haven’t had a look at it, please refer this link. In our last part, we saw how we can define routes for the application; also, we saw how to define Child routes for the featured modules of the application.

In this part, we’ll look what Auxiliary routes are and how we can achieve lazy loading through routing. So, first thing first, what Auxiliary routes are.

Auxiliary Routes

They are same as the normal route except with named router outlet i.e. while defining auxiliary route definition, you’ll be providing the router outlet name so as to specify where the contents should be displayed. Also, the router-outlet defined in your HTML code will have the name property defined. This is the most common scenario where we need to display master-details records on the same page next to each-other.

Syntax

  1. {path:yourURL Path, component: yourComponent, outlet: “routerOutletName”}   

In your HTML.

  1. <router-outlet name= “routerOutletName”></router-outlet>   

NOTE

The name defined for router-outlet element in your HTML should be same as defined in your route configuration outlet property value.

For demonstrating what Auxiliary routes are, we’ll create a new component with the name “EmployeeInfoComponent” where we’ll display the list of all employee Ids as hyperlink and on click of any employee ID, that particular employee record details should be displayed on the right hand side of the page, using EmployeeDetailsComponent which we’ve created in our 1st part. The below picture describes the same.


To achieve this, we need to do the below steps.

  1. Add new methods to “EmployeeService” as per our business requirement i.e. a function returning the list of all “EmpIds” and a function returning employee details based on “EmpId”. Here is the updated code snippet for the same.
    1. import { Injectable } from '@angular/core';  
    2. import { Http, Response } from '@angular/http';  
    3. import { IEmployee } from './employee';  
    4. import { Observable } from 'rxjs/Observable';  
    5.   
    6. @Injectable()  
    7. export class EmpService {  
    8.   
    9.     private _repositoryURL: string = "app/emp/emp-data.json";  
    10.   
    11.     constructor(private http: Http) {  
    12.     }  
    13.   
    14.     /** 
    15.      * GET's all employees 
    16.      */  
    17.     public getEmployees(): Observable<IEmployee[]> {  
    18.         return this.http.get(this._repositoryURL)  
    19.             .map((response: Response) => { return <IEmployee[]>response.json() })  
    20.             .catch(this.handleError);  
    21.     }  
    22.   
    23.     /** 
    24.      * GET employee ID's 
    25.      */  
    26.     public getEmployeeIDs(): Observable<string[]> {  
    27.         return this.http.get(this._repositoryURL)  
    28.             .map((response: Response) => {  
    29.                 return <string[]>(<IEmployee[]>response.json()).map(function (jsonObj: IEmployee) {  
    30.                     return jsonObj.empId  
    31.                 });  
    32.             })  
    33.             .catch(this.handleError);  
    34.     }  
    35.   
    36.     /** 
    37.      * GET employee based on parameter employeeID 
    38.      */  
    39.     public getEmployee(eid: string): Observable<IEmployee> {  
    40.         return this.http.get(this._repositoryURL)  
    41.             .map((response: Response) => {  
    42.                 return <IEmployee>(<IEmployee[]>response.json()).filter(function (objEmp: IEmployee, idx: number) {  
    43.                     return objEmp.empId === eid;  
    44.                 })[0];  
    45.             })  
    46.             .do(eData => console.log(eData))  
    47.             .catch(this.handleError);  
    48.     }  
    49.   
    50.     private handleError(errorResponse: Response) {  
    51.         console.log(errorResponse.statusText);  
    52.         return Observable.throw(errorResponse.json().error || "Server error");  
    53.     }  
    54. }  
  1. Create a new named router-outlet in your application root component HTML fragment. Below is the updated code snippet for “app.component.ts” file.
    1. import { Component } from '@angular/core';  
    2.   
    3. @Component({  
    4.   selector: 'my-app',  
    5.   template: `<nav class="navbar navbar-inverse">  
    6.                 <div class="container">  
    7.                   <div class="navbar-header">  
    8.                       <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false">  
    9.                           <span class="sr-only">Toggle navigation</span>  
    10.                           <span class="icon-bar"></span>  
    11.                           <span class="icon-bar"></span>  
    12.                           <span class="icon-bar"></span>  
    13.                       </button>  
    14.                       <a class="navbar-brand" href="#">{{orgName}}</a>  
    15.                   </div>  
    16.                   <div class="collapse navbar-collapse" id="navbar">  
    17.                     <ul class="nav navbar-nav">  
    18.                         <li><a routerLink="welcome" routerLinkActive="active">Home <span class="sr-only">(current)</span></a></li>  
    19.                         <li><a routerLink="emp-list" routerLinkActive="active">Emp-List</a></li>  
    20.                         <li><a routerLink="emp-info" routerLinkActive="active">E-Details</a></li>  
    21.                     </ul>   
    22.                   </div>  
    23.                 </div>  
    24.               </nav>  
    25.               <div class='container'>  
    26.                 <div class="col-md-7">  
    27.                    <router-outlet></router-outlet>  
    28.                 </div>   
    29.                 <div class="col-md-5">  
    30.                    <router-outlet name="detailsOutlet"></router-outlet>  
    31.                 </div>  
    32.               </div>`  
    33. })  
    34. export class AppComponent {  
    35.   orgName: string = "Angular2 Series";  
    36. }  

NOTE

  1. We’ve defined a named router-outlet “detailsOutlet”. Now, there are 2 router- outlets in our HTML code. By default, the one without name will be used by Angular for displaying child/navigated pages. When we specify that the data of a particular component to be displayed in the “detailsOutlet”, then only the details will be displayed in the “detailsOutlet” section.

  2. We’ve added a new menu-item named “emp-info” which will help us navigate to the newly created component “EmployeeInfoComponent”.
  1. Create EmployeeInfoComponent: A simple component which lists all employee Ids as hyperlink. Below is the code snippet for the same.
    1. import { Component, OnInit } from '@angular/core';  
    2. import { EmpService } from './emp.service';  
    3.   
    4. @Component({  
    5.     selector: 'emp-info',  
    6.     templateUrl: './app/emp/emp-info.component.html'  
    7. })  
    8. export class EmployeeInfoComponent implements OnInit {  
    9.     pageTitle: string = "Employee Info";  
    10.     imageWidth: number = 125;  
    11.     imageHeight: number = 125;  
    12.     errorMessage: string;  
    13.     eIds: string[];  
    14.   
    15.     constructor(private empService: EmpService) {  
    16.         this.eIds = [];  
    17.     }  
    18.     ngOnInit(): void {  
    19.         let self = this;  
    20.         self.empService.getEmployeeIDs().subscribe(empData => this.eIds = empData,  
    21.             error => this.errorMessage = <any>error);  
    22.     }  
    23. }  

NOTE

In ngOnInit method, we are loading all the employee Ids by calling the employee service method and storing all those in eId’s [].

  1. component.html
    1. <ul class="list-group" style="width: 450px;">  
    2.     <li class="list-group-item" *ngFor="let id of eIds">  
    3.         <a [routerLink]="['/emp-info',{outlets: {'detailsOutlet': ['emp-details',id]}}]">{{id}}</a>  
    4.     </li>  
    5. </ul>  

NOTE

  1. Look at the special syntax for defining routerLink with outlet property value.
  2. routerLink is defined as an array, since we are passing multiple parameters to it. That’s why it is enclosed in [] box brackets.
  3. “/emp-info” refers to the parent URL path.
  4. “outlets”: Angular2 property for defining outlet name.

    1. “detailsOutlet” is the name of our router-outlet element defined in our root Component HTML.
    2. “emp-details” refers to the child URL path where we need to navigate by passing employee Id as the parameter which is the last segment.
  1. We also need to update our EmployeeDetailsComponent where we need to load the employee details based on “employeeId” passed as query string parameter. In our first part on routing, we just left this component with text “Employee Details Component”; now, it’s time for us to write code for fetching employee details based on “employeeId”. Here is the updated code snippet for the same.
    1. import { Component, OnInit } from '@angular/core';  
    2. import { ActivatedRoute, Router, Params } from '@angular/router';  
    3. import { EmpService } from './emp.service';  
    4. import { IEmployee } from './employee';  
    5.   
    6. @Component({  
    7.     selector: "emp-details",  
    8.     templateUrl: "./app/emp/emp-details.component.html"  
    9. })  
    10. export class EmployeeDetailsComponent implements OnInit {  
    11.     pageTitle: string = "Employee Details";  
    12.     imageWidth: number = 125;  
    13.     imageHeight: number = 125;  
    14.     empId: string;  
    15.     empData: IEmployee;  
    16.     errorMessage: string;  
    17.   
    18.     constructor(private route: ActivatedRoute, private router: Router, private empService: EmpService) {  
    19.         this.empId = "";  
    20.         this.empData = null;  
    21.     }  
    22.   
    23.     ngOnInit(): void {  
    24.         this.route.params.subscribe(params => {  
    25.             //by default routed params will be of type string.   
    26.             this.empId = params["eid"];  
    27.   
    28.             this.empService.getEmployee(this.empId).subscribe(empData => this.empData = empData, error => this.errorMessage = <any>error);  
    29.         });  
    30.     }  
    31. }  
  1. Below is the updated HTML fragment for employee-details.component.html.
    1. <div class="col-md-12 text-center">  
    2.     <div class="panel panel-primary">  
    3.         <div class="panel-heading">  
    4.             <h4>Employee Details: {{empId}}</h4>  
    5.         </div>  
    6.         <div class="panel-body" *ngIf="empData!=null">  
    7.             <div class="row">  
    8.                 <div class="col-md-4">  
    9.                     <img [src]="empData.photo" [style.width.px]='imageWidth' [style.height.px]='imageHeight' />  
    10.                 </div>  
    11.                 <div class="col-md-8">  
    12.                     <table class="table table-striped table-bordered">  
    13.                         <tbody>  
    14.                             <tr>  
    15.                                 <td>Name: </td>  
    16.                                 <td class="leftAlign">{{empData.name|uppercase}}</td>  
    17.                             </tr>  
    18.                             <tr>  
    19.                                 <td>Email:</td>  
    20.                                 <td class="leftAlign">{{empData.email|lowercase}}</td>  
    21.                             </tr>  
    22.                             <tr>  
    23.                                 <td>Phone:</td>  
    24.                                 <td class="leftAlign">{{empData.phone}}</td>  
    25.                             </tr>  
    26.                             <tr>  
    27.                                 <td>Salary:</td>  
    28.                                 <td class="leftAlign" [formatSalary]='empData.salary' defaultTextColor="#0e0e6c">{{empData.salary|currency:'INR':true:'1.2-2'}}</td>  
    29.                             </tr>  
    30.                             <tr>  
    31.                                 <td>Appraisal Rating:</td>  
    32.                                 <td>  
    33.                                     <ar-star [appraisalRating]="empData.appraisalRating"></ar-star>  
    34.                                 </td>  
    35.                             </tr>  
    36.                         </tbody>  
    37.                     </table>  
    38.                 </div>  
    39.             </div>  
    40.         </div>  
    41.     </div>  
    42. </div>  
  1. Define routing configuration for Auxiliary routes. Here is the updated emp-route.config.ts file.
    1. import { Routes } from '@angular/router';  
    2.   
    3. import { EmployeeListComponent } from './emp-list.component'  
    4. import { EmployeeDetailsComponent } from './emp-details.component';  
    5. import { EmployeeInfoComponent } from './emp-info.component';  
    6.   
    7. export const eRoutes: Routes = [  
    8.     {  
    9.         path: 'emp-list',  
    10.         children: [  
    11.             { path: '', component: EmployeeListComponent },  
    12.             { path: 'emp-details/:eid', component: EmployeeDetailsComponent }  
    13.         ]  
    14.     },  
    15.     {  
    16.         path: 'emp-info',  
    17.         children: [  
    18.             { path: '', component: EmployeeInfoComponent },  
    19.             { path: 'emp-details/:eid', component: EmployeeDetailsComponent, outlet: "detailsOutlet" }  
    20.         ]  
    21.     }  
    22. ]   

NOTE

  1. We’ve defined “emp-info” as the parent path which has 2 child routes defined.
  2. If the path is empty then load “EmployeeInfoComponent. The default route.
  3. If path is “emp-details/:eid, then load EmployeeDetailsComponent. In the same path, we also specified outlet: “detailsOutlet” i.e. the EmployeeDetailsComponent should be loaded in the detailsOutlet router-outlet HTML element. Remember, we’ve defined this outlet in our root component with name “detailsOutlet”. The same name is passed here.
  1. Registering & exporting our newly created component with/from emp-module. Here is the updated code snippet for the same.

    emp-module.ts
    1. import { NgModule } from '@angular/core';  
    2. import { FormsModule } from '@angular/forms';  
    3. import { RouterModule } from '@angular/router';  
    4. import { CommonModule } from '@angular/common';  
    5. import { EmployeeListComponent } from './emp-list.component';  
    6. import { EmployeeSearchPipe } from './emp-search.pipe';  
    7. import { FormatSalaryDirective } from './emp-formatsalary.directive';  
    8. import { EmpService } from './emp.service';  
    9. import { SharedModule } from '../shared/shared.module';  
    10. import { EmployeeDetailsComponent } from './emp-details.component';  
    11. import { EmployeeInfoComponent } from './emp-info.component';  
    12.   
    13. @NgModule({  
    14.     imports: [CommonModule, FormsModule, SharedModule, RouterModule],  
    15.     declarations: [EmployeeListComponent, EmployeeSearchPipe, FormatSalaryDirective, EmployeeDetailsComponent, EmployeeInfoComponent],  
    16.     providers: [EmpService],  
    17.     exports: [EmployeeListComponent, EmployeeDetailsComponent, EmployeeInfoComponent]//we have to export EmployeeListComponent since we are using it in our app.component.ts file template. If we don't export it will have private access by default i.e. available only within that module  
    18. })  
    19. export class EmpModule {}  

That’s it. We are done. Try running the application and you’ll see the below output on click of any employeeID. Hereby, displaying the master-detail record.


This is what Auxiliary routes are. Now that you know, you can further explore it in your own way.

Lazy loading modules in Routing

Lazy loading, in simple terms, means load something on demand. We can implement lazy loading in Angular 2 by means of Routing and featured modules. In lazy loading, until and unless a particular module is requested, it won’t be loaded. The benefit of doing so is that it will reduce the startup/bootstrap time of the application. On the initial load, the application will load only the necessary files and rest of the modules/files will be loaded only when user tries to access them. For demonstrating this, we’ll convert our eager loading application to lazy loading.

For now, we’ve separate modules which are finally loaded in our root application module. This is called eager loading i.e. loading everything before-hand; even if the user doesn’t access our “emp-list” or “emp-info” component, it will still be loaded at the startup time.


I’m accessing home page of our application but in the network tab, you can see the application loads everything including the “employee-module” which is not been accessed by the user. Below snap describes the same.


To lazy load things, we need to make a couple of changes.

  1. Remove featured modules imported from our application root module from app.module.ts file. So from our root module file, we’ll remove “empModule” import. Here is the updated code snippet for the same.
    1. /** Importing the angular modules from namespace */  
    2. import { NgModule } from '@angular/core';  
    3. import { FormsModule } from '@angular/forms';  
    4. import { BrowserModule } from '@angular/platform-browser';  
    5. import { HttpModule } from '@angular/http';  
    6. import { RouterModule, Routes } from '@angular/router';  
    7. /** Importing the AppComponent from the application's app folder */  
    8. import { AppComponent } from './app.component';  
    9. import 'rxjs/Rx';  
    10. import { routeConfig } from './app.route-config';  
    11. import { WelcomeComponent } from './welcome/welcome.component';  
    12.   
    13. @NgModule({  
    14.   imports: [BrowserModule, FormsModule, HttpModule, RouterModule.forRoot(routeConfig)],//other modules whose exported classes are needed by component templates declared in this module.  
    15.   declarations: [AppComponent, WelcomeComponent],// the view classes that belong to this module. Angular has three kinds of view classes: components, directives, and pipes.  
    16.   bootstrap: [AppComponent]//the main application view, called the root component, that hosts all other app views. Only the root module should set this bootstrap property.  
    17. })  
    18. export class AppModule { }  
  1. Update our application root route config file i.e. app.route-config.ts to lazy load things using the loadChildren property.
    1. import { Routes } from '@angular/router';  
    2.   
    3. import { WelcomeComponent } from './welcome/welcome.component';  
    4.   
    5. export const routeConfig: Routes = [  
    6.     {  
    7.         path: 'welcome',  
    8.         component: WelcomeComponent  
    9.     },  
    10.     {  
    11.         path: 'empLazy',  
    12.         loadChildren: 'app/emp/emp.module#EmpModule'  
    13.     },  
    14.     {  
    15.         //default route  
    16.         path: '',  
    17.         component: WelcomeComponent  
    18.     },  
    19.     {  
    20.         //fallback mechanism  
    21.         path: '**',  
    22.         component: WelcomeComponent  
    23.     }  
    24. ]  
    NOTE

    As you can see that I’ve created one path named “empLazy” and using the loadChildren property, I’m lazily loading the “EmpModule”.
  1. Update your routerLink to the new path i.e. “empLazy”. I am updating our app.component.ts file where we specified our routerLink HTML attribute value. Below is the updated code snippet for the same.
    1. import { Component } from '@angular/core';  
    2.   
    3. @Component({  
    4.   selector: 'my-app',  
    5.   template: `<nav class="navbar navbar-inverse">  
    6.                 <div class="container">  
    7.                   <div class="navbar-header">  
    8.                       <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false">  
    9.                           <span class="sr-only">Toggle navigation</span>  
    10.                           <span class="icon-bar"></span>  
    11.                           <span class="icon-bar"></span>  
    12.                           <span class="icon-bar"></span>  
    13.                       </button>  
    14.                       <a class="navbar-brand" href="#">{{orgName}}</a>  
    15.                   </div>  
    16.                   <div class="collapse navbar-collapse" id="navbar">  
    17.                     <ul class="nav navbar-nav">  
    18.                         <li><a routerLink="welcome" routerLinkActive="active">Home <span class="sr-only">(current)</span></a></li>  
    19.                         <li><a routerLink="empLazy/emp-list" routerLinkActive="active">Emp-List</a></li>  
    20.                         <li><a routerLink="empLazy/emp-info" routerLinkActive="active">E-Details</a></li>  
    21.                     </ul>   
    22.                   </div>  
    23.                 </div>  
    24.               </nav>  
    25.               <div class='container'>  
    26.                 <div class="col-md-7">  
    27.                    <router-outlet></router-outlet>  
    28.                 </div>   
    29.                 <div class="col-md-5">  
    30.                    <router-outlet name="detailsOutlet"></router-outlet>  
    31.                 </div>  
    32.               </div>`  
    33. })  
    34. export class AppComponent {  
    35.   orgName: string = "Angular2 Series";  
    36. }  
    NOTE

    This time, we set the routerLink to “empLazy/emp-list” and “empLazy/emp-info” where empLazy will load our EmpModule.
  1. Create the routes configuration for the EmpModule. Here is the updated code snippet for the same: emp-route.config.ts file.
    1. import { Routes } from '@angular/router';  
    2.   
    3. import { EmployeeListComponent } from './emp-list.component'  
    4. import { EmployeeDetailsComponent } from './emp-details.component';  
    5. import { EmployeeInfoComponent } from './emp-info.component';  
    6.   
    7. export const eRoutes: Routes = [  
    8.     {  
    9.         path: 'emp-list',  
    10.         children: [  
    11.             { path: '', component: EmployeeListComponent },  
    12.             { path: 'emp-details/:eid', component: EmployeeDetailsComponent }  
    13.         ]  
    14.     },  
    15.     {  
    16.         path: 'emp-info',  
    17.         children: [  
    18.             { path: '', component: EmployeeInfoComponent },  
    19.             { path: 'emp-details/:eid', component: EmployeeDetailsComponent, outlet: "detailsOutlet" }  
    20.         ]  
    21.     }  
  1. Update our EmpModule to load the Router configuration for the module using the “RouterModule.forChild() method”. Here is the updated code snippet for “emp-module.ts” file.
    1. import { NgModule } from '@angular/core';  
    2. import { FormsModule } from '@angular/forms';  
    3. import { RouterModule } from '@angular/router';  
    4. import { CommonModule } from '@angular/common';  
    5. import { EmployeeListComponent } from './emp-list.component';  
    6. import { EmployeeSearchPipe } from './emp-search.pipe';  
    7. import { FormatSalaryDirective } from './emp-formatsalary.directive';  
    8. import { EmpService } from './emp.service';  
    9. import { SharedModule } from '../shared/shared.module';  
    10. import { EmployeeDetailsComponent } from './emp-details.component';  
    11. import { EmployeeInfoComponent } from './emp-info.component';  
    12. import { eRoutes } from './emp-route.config';  
    13.   
    14.   
    15. @NgModule({  
    16.     imports: [CommonModule, FormsModule, SharedModule, RouterModule.forChild(eRoutes)],  
    17.     declarations: [EmployeeListComponent, EmployeeSearchPipe, FormatSalaryDirective, EmployeeDetailsComponent, EmployeeInfoComponent],  
    18.     providers: [EmpService],  
    19.     exports: [EmployeeListComponent, EmployeeDetailsComponent, EmployeeInfoComponent]//we have to export EmployeeListComponent since we are using it in our app.component.ts file template. If we don't export it will have private access by default i.e. available only within that module  
    20. })  
    21. export class EmpModule {  
  1. Update our routerLink attribute for “emp-info.component.html” so that routing works in case of Auxiliary routes too. Here is the update code snippet for the same.
    1. <ul class="list-group" style="width:450px;">  
    2.     <li class="list-group-item" *ngFor="let id of eIds">  
    3.         <a [routerLink]="['/empLazy/emp-info',{outlets: {'detailsOutlet': ['emp-details',id]}}]">{{id}}</a>  
    4.     </li>  
    5. </ul>  

Try running the application and on landing of our home page, check network tab. You’ll only find that the resources required by the home page are loaded.

Below is the snapshot for the same.


Now, try clicking the EmpList or EDetails menu item. You’ll find that the EmpModule will get loaded as soon as you click on any of these links.


Our app is working as expected after implementing lazy loading. Try checking it.


On click of EmpId, you’ll be navigated to the EmployeeDetailsComponent. Below is the snap for the same.


On click of E-Details, you’ll be navigated to EmployeeInfoComponent.


Now that you know lazy loading you can further explore it. I am uploading the solution file with this post; but I won’t be able to upload the node_modules folder because of the heavy size. Thus, I request you to install NPM before you run the application.