Angular 2/4 Child Routes and Relative Navigation Example

,


Understanding Child Routes

Suppose base URL is "/" configured by <base href="/"> in index.html. We will create a diagram that will display the child routes for a component. The components shown in diagram will be used in our demo application.


Find the child routes corresponding to the components.

1. URL: /
Components: AppComponent

2. URL: /country
Components: AppComponent -> CountryComponent

3. URL: /country/add
Components: AppComponent -> CountryComponent -> AddCountryComponent

4. URL: /country/list
Components: AppComponent -> CountryComponent -> CountryListComponent

5. URL: /country/list/view/1
Components: AppComponent -> CountryComponent -> CountryListComponent -> CountryDetailComponent

6. URL: /country/list/edit/1
Components: AppComponent -> CountryComponent -> CountryListComponent -> CountryEditComponent

7. URL: /person
Component: AppComponent -> PersonComponent -> PersonListComponent

8. URL: /person/1
Component: AppComponent -> PersonComponent -> PersonListComponent -> PersonEditComponent

9. URL: /address
Component: AppComponent -> AddressComponent

In our example we have two main features. Every feature resides in its own folder with its associated files. We will provide code feature wise.

1. Project Structure
Find the project structure of our demo application.

angular-demo
|
|--src
|   |
|   |--app 
|   |   |
|   |   |--country
|   |   |    | 
|   |   |    |--country.component.ts
|   |   |    |--country.ts
|   |   |    |--country.module.ts
|   |   |    |--country-routing.module.ts
|   |   |    |
|   |   |    |--add-country
|   |   |    |    |  
|   |   |    |    |--add-country.component.html
|   |   |    |    |--add-country.component.ts
|   |   |    |    |
|   |   |    |--country-list
|   |   |    |    |
|   |   |    |    |--country.list.component.html
|   |   |    |    |--country.list.component.ts
|   |   |    |    |
|   |   |    |    |--detail
|   |   |    |    |   |
|   |   |    |    |   |--country.detail.component.html
|   |   |    |    |   |--country.detail.component.ts
|   |   |    |    |
|   |   |    |    |--edit
|   |   |    |    |   |
|   |   |    |    |   |--country.edit.component.html
|   |   |    |    |   |--country.edit.component.ts
|   |   |    |    |
|   |   |    |    |
|   |   |    |--service
|   |   |    |    |
|   |   |    |    |--country.service.ts
|   |   |
|   |   |--person
|   |   |    | 
|   |   |    |--person.component.ts
|   |   |    |--person.ts
|   |   |    |--person-routing.module.ts
|   |   |    |--person.module.ts
|   |   |    | 
|   |   |    |--person-list 
|   |   |    |    |
|   |   |    |    |--person.list.component.html 
|   |   |    |    |--person.list.component.ts
|   |   |    |    |
|   |   |    |    |--edit
|   |   |    |    |   |
|   |   |    |    |   |--person.edit.component.html
|   |   |    |    |   |--person.edit.component.ts
|   |   |    | 
|   |   |    |--service
|   |   |    |    |  
|   |   |    |    |--person.service.ts
|   |   |
|   |   |--address.component.ts
|   |   |--page-not-found.component.ts
|   |   |--app.component.ts
|   |   |--app-routing.module.ts
|   |   |--app.module.ts
|   |   
|   |--main.ts
|   |--index.html
|   |--styles.css
|
|--node_modules
|--package.json

2. Feature One Complete Code of Demo Application
Here we will provide complete code of country feature.

add-country.component.html

<h3>Add Country</h3>
<form [formGroup]="countryForm" (ngSubmit)="onFormSubmit()">
   <p> Name: <input formControlName="name"> </p>
   <p> Capital: <input formControlName="capital"> </p>
   <p> Currency: <input formControlName="currency"> </p>
   <p> <button>Add</button> </p>
</form>

add-country.component.ts

import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';

import { CountryService } from '../service/country.service';
import { Country } from '../country';
@Component({
    templateUrl: './add-country.component.html'
})
export class AddCountryComponent { 
 constructor(
  private countryService: CountryService,
  private route: ActivatedRoute,
                private router: Router) { }
  
 countryForm = new FormGroup({
    id: new FormControl(),
    name: new FormControl(),
    capital: new FormControl(),
    currency: new FormControl()
 }); 
 onFormSubmit() {
    let name = this.countryForm.get('name').value;
    let capital = this.countryForm.get('capital').value;
    let currency = this.countryForm.get('currency').value;
    
    let country = new Country(null, name, capital, currency);
    this.countryService.addCountry(country)
       .then(data =>
        this.router.navigate([ '../list/view', data.countryId ], { relativeTo: this.route })
           );
 }
} 

country.detail.component.html


<h3>Country Detail </h3>
<div *ngIf="country">
 <p><b>Id:</b> {{country.countryId}},  
 <b>Name:</b> {{country.countryName}} </p>
 <p><b>Capital:</b> {{country.capital}},
 <b>Currency:</b> {{country.currency}}</p>
</div> 

country.detail.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import 'rxjs/add/operator/switchMap';

import { CountryService } from '../../service/country.service';
import { Country } from '../../country';

@Component({
  templateUrl: './country.detail.component.html' 
}) 
export class CountryDetailComponent implements OnInit { 
    country: Country;
    constructor(
         private countryService: CountryService,
  private route: ActivatedRoute) { }
    ngOnInit() {
       this.route.params
        .switchMap((params: Params) => this.countryService.getCountry(+params['country-id']))
        .subscribe(country => this.country = country);
    }     
} 

country.edit.component.html

<h3>Edit Country</h3>
<p *ngIf="country"><b>Country Id: {{country.countryId }} </b></p>
<form [formGroup]="countryForm" (ngSubmit)="onFormSubmit()">
   <p> Name: <input formControlName="name"> </p>
   <p> Capital: <input formControlName="capital"> </p>
   <p> Currency: <input formControlName="currency"> </p>
   <p> <button>Update</button> </p>
</form>  

country.edit.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { FormControl, FormGroup } from '@angular/forms';
import 'rxjs/add/operator/switchMap';

import { CountryService } from '../../service/country.service';
import { Country } from '../../country';

@Component({
  templateUrl: './country.edit.component.html' 
}) 
export class CountryEditComponent implements OnInit { 
    country: Country;
    constructor(
  private countryService: CountryService,
  private route: ActivatedRoute,
                private router: Router) { }
  
    ngOnInit() {
       this.route.params
        .switchMap((params: Params) => this.countryService.getCountry(+params['country-id']))
         .subscribe(country => {
         this.country = country;
         this.setFormValues();
  }
  );
    } 
    countryForm = new FormGroup({
  name: new FormControl(),
  capital: new FormControl(),
  currency: new FormControl()
    }); 
    setFormValues() {
  this.countryForm.setValue({name: this.country.countryName, 
       capital: this.country.capital, currency: this.country.currency});
    } 
    onFormSubmit() {
 this.country.countryName = this.countryForm.get('name').value;
 this.country.capital = this.countryForm.get('capital').value;
 this.country.currency = this.countryForm.get('currency').value;
    
 this.countryService.updateCountry(this.country)
      .then(() =>
        this.router.navigate([ '../../' ], { relativeTo: this.route })
   );
 }
} 

country.list.component.html

<h3>Country List</h3>
<div *ngFor="let country of countries | async" [ngClass]= "'sub-child-menu'">
 <p>{{country.countryId}}. {{country.countryName}}
 <a [routerLink]="['view', country.countryId]" routerLinkActive="active">View</a> |
 <a [routerLink]="['edit', country.countryId]" routerLinkActive="active">Edit</a>
 </p>
</div>
<div [ngClass]= "'sub-child-container'">
 <router-outlet></router-outlet>  
</div> 

country.list.component.ts

import { Component, OnInit } from '@angular/core';

import { CountryService } from '../service/country.service';
import { Country } from '../country';

@Component({
  templateUrl: './country.list.component.html' 
}) 
export class CountryListComponent implements OnInit { 
  countries: Promise<Country[]>
  constructor(private countryService: CountryService) {}
  ngOnInit() {
    this.countries = this.countryService.getCountries();
  } 
} 

country.service.ts

import { Injectable } from '@angular/core';

import { Country } from '../country';

const COUNTRIES = [
  new Country(1, 'India', 'New Delhi', 'INR'),
  new Country(2, 'China', 'Beijing', 'RMB')
];
let countriesPromise = Promise.resolve(COUNTRIES);

@Injectable()
export class CountryService { 
     getCountries(): Promise<Country[]> {
     return countriesPromise;
     }
     getCountry(id: number): Promise<Country> {
        return this.getCountries()
            .then(countries => countries.find(country => country.countryId === id));
     } 
     updateCountry(country: Country): Promise<Country> {
  return this.getCountries()
   .then(countries => {
         let countryObj = countries.find(ob => ob.countryId === country.countryId);
                countryObj = country;
  return countryObj;
      }
   );
     } 
     addCountry(country: Country): Promise<Country> {
 return this.getCountries()
   .then(countries => {
       let maxIndex = countries.length - 1;
       let countryWithMaxIndex = countries[maxIndex];
       country.countryId = countryWithMaxIndex.countryId + 1;
       countries.push(country);
       return country;
     }
   );
     } 
} 

country.ts

export class Country { 
 constructor(public countryId:number, public countryName:string,
             public capital:string, public currency:string) {
 }
} 

country.component.ts

import { Component } from '@angular/core';
@Component({
  template: `<h2>Welcome to Country Home</h2>
   <nav [ngClass] = "'child-menu'">
      <ul>
 <li><a [routerLink]="['add']" routerLinkActive="active">Add Country</a></li>
 <li><a [routerLink]="['list']" routerLinkActive="active">Country List</a></li>
      </ul>  
   </nav>  
   <div [ngClass] = "'child-container'"> 
   <router-outlet></router-outlet> 
   </div>
  `
})
export class CountryComponent { 
} 

country-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { CountryComponent }  from './country.component';
import { CountryListComponent }  from './country-list/country.list.component';
import { AddCountryComponent }  from './add-country/add-country.component';
import { CountryDetailComponent }  from './country-list/detail/country.detail.component';
import { CountryEditComponent }  from './country-list/edit/country.edit.component';

const countryRoutes: Routes = [
 { 
   path: 'country',
          component: CountryComponent,
          children: [ 
     {
     path: 'add',
     component: AddCountryComponent
     },
     {
            path: 'list',
     component: CountryListComponent,
     children: [
         {
      path: 'view/:country-id',
             component: CountryDetailComponent
         },
         {
      path: 'edit/:country-id',
             component: CountryEditComponent
         },      
     ]
     } 
   ]
 }  
];

@NgModule({
  imports: [ RouterModule.forChild(countryRoutes) ],
  exports: [ RouterModule ]
})
export class CountryRoutingModule{ } 

country.module.ts

import { NgModule }   from '@angular/core';
import { CommonModule }   from '@angular/common';
import { ReactiveFormsModule }    from '@angular/forms';

import { CountryComponent }  from './country.component';
import { AddCountryComponent }  from './add-country/add-country.component';
import { CountryListComponent }  from './country-list/country.list.component';
import { CountryDetailComponent }  from './country-list/detail/country.detail.component';
import { CountryEditComponent }  from './country-list/edit/country.edit.component';
import { CountryService } from './service/country.service';
import { CountryRoutingModule }  from './country-routing.module';

@NgModule({
  imports: [     
        CommonModule,
 ReactiveFormsModule,
 CountryRoutingModule
  ], 
  declarations: [
 CountryComponent,
 AddCountryComponent,
 CountryListComponent,
 CountryEditComponent,
 CountryDetailComponent
  ],
  providers: [ CountryService ]
})
export class CountryModule { } 

CommonModule includes all the basic Angular directives such as NgIf, NgFor, NgClass etc.

3. Feature Two Complete Code of Demo Application
Here we will provide complete code of person feature.

person.edit.component.html

<h3>Edit Person</h3>
<p *ngIf="person"><b>Person Id: {{person.personId }} </b></p>
<form [formGroup]="personForm" (ngSubmit)="onFormSubmit()">
   <p> Name: <input formControlName="name"> </p>
   <p> City: <input formControlName="city"> </p>
   <p> <button>Update</button> </p>
</form> 

person.edit.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, Params } from '@angular/router';
import { FormControl, FormGroup } from '@angular/forms';
import 'rxjs/add/operator/switchMap';

import { PersonService } from '../../service/person.service';
import { Person } from '../../person';

@Component({
  templateUrl: './person.edit.component.html' 
}) 
export class PersonEditComponent implements OnInit { 
        person: Person;
 constructor(
  private personService: PersonService,
  private route: ActivatedRoute,
                private router: Router) { }
  
        ngOnInit() {
          this.route.params
            .switchMap((params: Params) => this.personService.getPerson(+params['id']))
            .subscribe(person => {
              this.person = person;
       this.setFormValues();
   }
     );
        } 
 personForm = new FormGroup({
    name: new FormControl(),
    city: new FormControl()
 }); 
 setFormValues() {
    this.personForm.setValue({name: this.person.name, city: this.person.city});
 } 
 onFormSubmit() {
    this.person.name = this.personForm.get('name').value;
    this.person.city = this.personForm.get('city').value;
    
    this.personService.updatePerson(this.person)
      .then(() =>
        this.router.navigate([ '../' ], { relativeTo: this.route })
   );
 }
} 

person.list.component.html

<h3>Person List</h3>
<div *ngFor="let person of persons | async" [ngClass]= "'sub-child-menu'">
<p>{{person.personId}}. {{person.name}}, {{person.city}}
   <button type="button" (click)="goToEdit(person)">Edit</button>
</p>
</div>
<div [ngClass]= "'sub-child-container'">
 <router-outlet></router-outlet>  
</div>  

person.list.component.ts

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

import { PersonService } from '../service/person.service';
import { Person } from '../person';

@Component({
  templateUrl: './person.list.component.html' 
}) 
export class PersonListComponent implements OnInit { 
  persons: Promise<Person[]>
  constructor(  
        private personService: PersonService,
        private route: ActivatedRoute,
        private router: Router) {}
  ngOnInit() {
      this.persons = this.personService.getPersons();
  } 
  goToEdit(person:Person) {
      this.router.navigate([ person.personId ], { relativeTo: this.route });
  }
} 

person.service.ts

import { Injectable } from '@angular/core';

import { Person } from '../person';

const PERSONS = [
  new Person(1, 'Mahesh', 'Varanasi'),
  new Person(2, 'Ram', 'Ayodhya'),  
  new Person(3, 'Kishna', 'Mathura')
];
let personsPromise = Promise.resolve(PERSONS);

@Injectable()
export class PersonService { 
 getPersons(): Promise<Person[]> {
     return personsPromise;
 }
 getPerson(id: number): Promise<Person> {
           return this.getPersons()
            .then(persons => persons.find(person => person.personId === id));
        } 
        updatePerson(person: Person): Promise<Person> {
     return this.getPersons()
    .then(persons => {
          let personObj = persons.find(ob => ob.personId === person.personId);
                        personObj = person;
   return personObj;
       }
    );
        } 
} 

person.ts

export class Person { 
 constructor(public personId:number, public name:string, public city:string) {
 }
} 

person.component.ts

import { Component } from '@angular/core';
@Component({
  template: `<h2>Welcome to Person Home</h2>
      <div [ngClass] = "'child-container'"> 
         <router-outlet></router-outlet> 
      </div>
  `
})
export class PersonComponent { 
} 

person-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { PersonComponent }  from './person.component';
import { PersonListComponent }  from './person-list/person.list.component';
import { PersonEditComponent }  from './person-list/edit/person.edit.component';

const personRoutes: Routes = [
 { 
   path: 'person',
          component: PersonComponent,
   children: [ 
     {
  path: '',
  component: PersonListComponent,
  children: [
     {
       path: ':id',
       component: PersonEditComponent
     }
  ]   
     }
   ]
 }  
];

@NgModule({
  imports: [ RouterModule.forChild(personRoutes) ],
  exports: [ RouterModule ]
})
export class PersonRoutingModule{ } 

person.module.ts

import { NgModule }   from '@angular/core';
import { CommonModule }   from '@angular/common';
import { ReactiveFormsModule }    from '@angular/forms';

import { PersonComponent }  from './person.component';
import { PersonListComponent }  from './person-list/person.list.component';
import { PersonEditComponent }  from './person-list/edit/person.edit.component';
import { PersonService } from './service/person.service';
import { PersonRoutingModule }  from './person-routing.module';

@NgModule({
  imports: [     
        CommonModule,
 ReactiveFormsModule,
 PersonRoutingModule
  ], 
  declarations: [
 PersonComponent,
 PersonListComponent,
 PersonEditComponent
  ],
  providers: [ PersonService ]
})
export class PersonModule { } 

4. Application Module and Other Components
Find application module and other components.

address.component.ts

import { Component } from '@angular/core';
@Component({
  template: `
      <h3>ADDRESS</h3>
          <p><b> Article: Child routing & Relative navigation </b></p>
   <p><b> Category: Angular </b></p>
   <p><b> Website: prohcj.co.in </b></p>
     <div>
         <a [routerLink]="['/location']">Find Location</a>
     </div> 
  `
})
export class AddressComponent { 
} 

page-not-found.component.ts

import { Component } from '@angular/core';
import { Location } from '@angular/common';

@Component({
  template: `<h2>Page Not Found.</h2>
             <div>
                <button (click)="goBack()">Go Back</button>
      </div>
            `
})
export class PageNotFoundComponent {
 constructor(private location: Location) { }
 goBack(): void {
           this.location.back();
        }
} 

app.component.ts

import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  template: `
 <nav [ngClass] = "'parent-menu'">
   <ul>
   <li><a routerLink="/country" routerLinkActive="active">Country</a></li>
   <li><a routerLink="/person" routerLinkActive="active">Person</a></li>
   <li><a routerLink="/address" routerLinkActive="active">Address</a></li>
   </ul> 
 </nav>  
 <div [ngClass] = "'parent-container'"> 
   <router-outlet></router-outlet> 
 </div>
  `
})
export class AppComponent { 
} 

app-routing.module.ts

import { NgModule }      from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AddressComponent }  from './address.component';
import { PageNotFoundComponent }  from './page-not-found.component';

const routes: Routes = [
 {
    path: 'country',
           loadChildren: 'app/country/country.module#CountryModule',
           data: { preload: true }
 },
 {
    path: 'person',
           loadChildren: 'app/person/person.module#PersonModule'
 },
 {
    path: 'address',
    component: AddressComponent
 }, 
 {
    path: '',
    redirectTo: '/country',
    pathMatch: 'full'
 },
        {
    path: '**',
    component: PageNotFoundComponent 
        } 
];
@NgModule({
  imports: [ 
          RouterModule.forRoot(routes) 
  ],
  exports: [ 
          RouterModule 
  ]
})
export class AppRoutingModule{ }  

app.module.ts

import { NgModule }   from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent }  from './app.component';
import { AddressComponent }  from './address.component';
import { PageNotFoundComponent }  from './page-not-found.component';

import { CountryModule }  from './country/country.module';
import { PersonModule }  from './person/person.module';
import { AppRoutingModule }  from './app-routing.module';

@NgModule({
  imports: [     
        BrowserModule,
 CountryModule,
 PersonModule,
 AppRoutingModule,
  ],
  declarations: [
        AppComponent,
 AddressComponent,
 PageNotFoundComponent
  ],
  providers: [ ],
  bootstrap: [ AppComponent ]
})
export class AppModule { } 

index.html

<!doctype html>
<html>
<head>
 <meta charset="utf-8">
 <title>Angular Demo</title>
 <base href="/">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="icon" type="image/x-icon" href="favicon.ico">
 <link rel="stylesheet" href="styles.css">
</head>
<body>
 <app-root>Loading...</app-root>
</body>
</html> 

styles.css

.parent-menu ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    background-color: #333;
}
.parent-menu li {
    float: left;
}
.parent-menu li a {
    display: block;
    color: white;
    text-align: center;
    padding: 15px 15px;
    text-decoration: none;
}
.parent-menu li a:hover:not(.active) {
    background-color: #111;
}
.parent-menu .active{
    background-color: #4CAF50;
}
.parent-container {
    padding-left: 10px;
}
.child-container {
    padding-left: 10px;
}
.sub-child-container {
    padding-left: 10px;
}
.child-menu  {
    padding-left: 25px;
}
.child-menu .active{
    color: #4CAF50;
}
.sub-child-menu {
    background-color: #f1f1f1;  
    width: 275px;
    list-style-type: none; 
    margin: 0;
    padding: 0;
}
.sub-child-menu .active{
    color: #4CAF50;
}
button {
    background-color: #008CBA;
    color: white;
} 






Related Post


Latest Post


Recent Posts Widget

Make sure to never miss a thing...

Get the latest news from the creative industry along with other creative goodies, conveniently delivered to social media.