Angular Resolve Guard Example

By Arvind Rai, August 30, 2021
This page will walk through Angular Resolve guard example. Angular provides Resolve interface with resolve method. To create a Angular Resolve guard, we need to create a class by implementing Resolve interface. Resolve guard is used in the scenario when we want to ensure whether there is data available or not before navigating to any route. If there is no data then it has no meaning to navigate there. It means we have to resolve data before navigating to that route. Here comes the role of Angular Resolve guard. To use Resolve guard we need to create a class by implementing Resolve interface and define resolve method. The resolve method returns Observable or Promise or a synchronous value. After creating resolver class, we need to configure it in providers metadata of @NgModule decorator in application module and then we need to configure our resolver class in route configuration using resolve property of Angular Route interface. We can also provide a function with the resolve signature to use it as resolve guard.
Here on this page we will provide complete example to use Resolve guard with Observable as well as Promise step- by-step.

Technologies Used

Find the technologies being used in our example.
1. Angular 12.1.0
2. Node.js 12.14.1
3. NPM 7.20.3

Resolve Interface

To create a Resolve route guard, we need to create a class implementing Angular Resolve interface. Find the Resolve interface structure from Angular doc.
interface Resolve<T> { 
  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T
}
It has a method named as resolve with arguments ActivatedRouteSnapshot and RouterStateSnapshot. The method resolve can return Observable or Promise or a synchronous value. Resolve interface is imported from @angular/router API.

Suppose we have to display country list and on the click of any country, we need to display its detail on the basis of country id. If we want to resolve country detail before going to country detail route, we need to define resolve method in such a way that first we will fetch country detail by id and if there is the data then that country detail will be returned and if there is no data then we will navigate to previous route and return null.

Resolve with Observable

We will provide here how to use Resolve interface with Observable. We will discuss it step by step.

Step-1: Create a class implementing Resolve interface. Suppose we want to display country list and on the click of country, we will display country detail using country id. If there is no data for the country id then we want to remain on the same route. So we need to resolve country detail before navigating to country detail route. To resolve country detail, we will create a class by implementing Resolve interface. Here we have created CountryDetailResolver as following.
@Injectable()
export class CountryDetailResolver implements Resolve<Country | null> {
  constructor(private countryService: CountryService, private router: Router) { }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Country | null> {
    let id = route.paramMap.get('country-id') ?? '';
    console.log('Resolving for country id:' + id);

    return this.countryService.getCountry(id).pipe(
      map(country => {
        if (country) {
          return country;
        } else {
          this.router.navigate(['/country/countryList']);
          return null;
        }
      }));
  }
} 
In the above code resolve is returning Observable<Country>. If country detail is available for the country id then country detail will be returned and if country detail is not available then we are navigating to previous route. In our service we have created a method that will return country object for the country id as Observable.
getCountry(id: string): Observable<Country | undefined> {
    return countriesObservable.pipe(
      map(countries => countries.find(country => country.countryId === id))
    );
} 
Step-2: We need to configure this resolver class in providers metadata of @NgModule decorator as following.
@NgModule({
   providers: [ CountryDetailResolver ],
   ------
})
export class CountryRoutingModule { } 
Step-3: Now we will configure our CountryDetailResolver in route configurations using resolve property of Angular Route interface.
const countryRoutes: Routes = [
  { 
    path: '',
    component: CountryComponent,
    children: [ 
     {
       path: 'countryList',
       component: CountryListComponent,
       children: [
         {
            path: 'detail/:country-id',
     	    component: CountryDetailComponent,
  	    resolve: {
 	      countryDetail: CountryDetailResolver
	    }
         }	   
       ]
      }	
    ]
  }  
]; 

Resolve with Promise

Here we will provide how to use Resolve interface with Promise . Find the code step by step.

Step-1: Create a class implementing Resolve interface. Suppose we have person list and on the click of person we want to display person detail by person id. If there is no data for the person id then we want to remain on the same route. So we need to resolve person detail before navigating to person detail route. To resolve person detail we have created PersonDetailResolver as following.
@Injectable()
export class PersonDetailResolver implements Resolve<Person | null> {
  constructor(private personService: PersonService, private router: Router) { }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Person | null> {
    let id = route.paramMap.get('person-id') ?? '';
    console.log('Resolving for person id:' + id);

    return this.personService.getPerson(id).then(person => {
      if (person) {
        return person;
      } else {
        this.router.navigate(['/person/personList']);
        return null;
      }
    });
  }
} 
We have created a class PersonDetailResolver by implementing Resolve interface. The method resolve is returning Promise . What we are doing here is that when we try to display person detail, first our resolver runs and resolves the person detail by person id before navigating to person detail route. If person is not found for the person id then we navigate to previous route and return null otherwise we return person detail object. In our service we have created a method that returns person object for the person id as Promise .
getPerson(id: string): Promise<Person | undefined> {
    return personsPromise
      .then(persons => persons.find(person => person.personId === id));
} 
Step-2: We need to configure this resolver class in providers metadata of @NgModule decorator as following.
@NgModule({
   providers: [ PersonDetailResolver ],
   ------
})
export class PersonRoutingModule { } 
Step-3: Now we will configure our PersonDetailResolver in route configurations using resolve property of Angular Route interface.
const personRoutes: Routes = [
  { 
    path: '',
    component: PersonComponent,
    children: [ 
     {
       path: 'personList',
       component: PersonListComponent,
       children: [
         {
           path: 'detail/:person-id',
	   component: PersonDetailComponent,
	   resolve: {
	     personDetail: PersonDetailResolver
	   }
         }
       ]			
     }
    ]
  }  
]; 


Complete Example

Find the project structure of our application.
my-app
|
|--src
|   |
|   |--app 
|   |   |
|   |   |--country
|   |   |    |
|   |   |    |--country-detail.resolver.ts 
|   |   |    |--country.component.ts
|   |   |    |--country.ts
|   |   |    |--country.module.ts
|   |   |    |--country-routing.module.ts
|   |   |    |
|   |   |    |--country-list
|   |   |    |    |
|   |   |    |    |--country.list.component.html
|   |   |    |    |--country.list.component.ts
|   |   |    |    |
|   |   |    |    |--detail
|   |   |    |    |   |
|   |   |    |    |   |--country.detail.component.html
|   |   |    |    |   |--country.detail.component.ts
|   |   |    |    |
|   |   |    |    
|   |   |    |--services
|   |   |    |    |
|   |   |    |    |--country.service.ts
|   |   |    | 
|   |   |
|   |   |--person
|   |   |    |
|   |   |    |--person-detail.resolver.ts 
|   |   |    |--person.component.ts
|   |   |    |--person.ts
|   |   |    |--person-routing.module.ts
|   |   |    |--person.module.ts
|   |   |    | 
|   |   |    |--person-list 
|   |   |    |    |
|   |   |    |    |--person.list.component.html 
|   |   |    |    |--person.list.component.ts
|   |   |    |    |
|   |   |    |    |--detail
|   |   |    |    |   |
|   |   |    |    |   |--person.detail.component.html
|   |   |    |    |   |--person.detail.component.ts
|   |   |    | 
|   |   |    |--services
|   |   |    |    |  
|   |   |    |    |--person.service.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
Now find the complete code. In our example we have country feature and person feature. First find the code for country feature.
country-detail.resolver.ts
import { Injectable } from '@angular/core';
import {
  Router, Resolve, RouterStateSnapshot,
  ActivatedRouteSnapshot
} from '@angular/router';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { CountryService } from './services/country.service';
import { Country } from './country';

@Injectable()
export class CountryDetailResolver implements Resolve<Country | null> {
  constructor(private countryService: CountryService, private router: Router) { }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Country | null> {
    let id = route.paramMap.get('country-id') ?? '';
    console.log('Resolving for country id:' + id);

    return this.countryService.getCountry(id).pipe(
      map(country => {
        if (country) {
          return country;
        } else {
          this.router.navigate(['/country/countryList']);
          return null;
        }
      }));
  }
} 
country.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { Country } from '../country';

const COUNTRIES: Country[] = [
  { "countryId": "1", "name": "India", "capital": "New Delhi", "currency": "INR" },
  { "countryId": "2", "name": "China", "capital": "Beijing", "currency": "RMB" }
];
let countriesObservable = of(COUNTRIES);
let countriesToDisplayObservable =
  of(COUNTRIES.concat({ "countryId": "3", "name": "UK", "capital": "London", "currency": "GBP" }));

@Injectable()
export class CountryService {
  getCountries(): Observable<Country[]> {
    return countriesToDisplayObservable;
  }
  getCountry(id: string): Observable<Country | undefined> {
    return countriesObservable.pipe(
      map(countries => countries.find(country => country.countryId === id))
    );
  }
} 
country.ts
export interface Country { 
  countryId: string;
  name: string;
  capital: string;
  currency: string;
} 
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 { CountryDetailComponent }  from './country-list/detail/country.detail.component';
import { CountryDetailResolver } from './country-detail.resolver';

const countryRoutes: Routes = [
	{ 
	    path: '',
	    component: CountryComponent,
            children: [ 
	     {
	       path: 'countryList',
	       component: CountryListComponent,
	       children: [
		 {
		   path: 'detail/:country-id',
		   component: CountryDetailComponent,
		   resolve: {
		     countryDetail: CountryDetailResolver
		   }
		 }	   
	       ]
	      }	
	    ]
	}  
];

@NgModule({
        imports: [ RouterModule.forChild(countryRoutes) ],
	exports: [ RouterModule ],
	providers: [ CountryDetailResolver ]
})
export class CountryRoutingModule { } 
country.module.ts
import { NgModule }   from '@angular/core';
import { CommonModule }   from '@angular/common';
import { CountryComponent }  from './country.component';
import { CountryListComponent }  from './country-list/country.list.component';
import { CountryDetailComponent }  from './country-list/detail/country.detail.component';
import { CountryService } from './services/country.service';
import { CountryRoutingModule }  from './country-routing.module';

@NgModule({
  imports: [     
        CommonModule,
	CountryRoutingModule
  ], 
  declarations: [
	CountryComponent,
	CountryListComponent,
	CountryDetailComponent
  ],
  providers: [ CountryService ]
})
export class CountryModule { } 
country.component.ts
import { Component } from '@angular/core';

@Component({
  template: `<h2>Welcome to Country Home</h2>
  	     <div ngClass = "child-container">	
	       <router-outlet></router-outlet>	
  	     </div>
  `
})
export class CountryComponent { 
} 
country.list.component.html
<h3>Country List</h3>
<div *ngFor="let country of countries | async" ngClass= "sub-child-menu">
  <p> {{country.name}}
	| <a [routerLink]="['detail', country.countryId]" routerLinkActive="active">Detail</a>
  </p>
</div>
<div ngClass= "sub-child-container">
  <router-outlet></router-outlet>  
</div> 
country.list.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { CountryService } from '../services/country.service';
import { Country } from '../country';

@Component({
  templateUrl: './country.list.component.html'
})
export class CountryListComponent implements OnInit {
  countries: Observable<Country[]>;
  constructor(private countryService: CountryService) {
    this.countries = this.countryService.getCountries();
  }
  ngOnInit() {
  }
} 
country.detail.component.html
<h3>Country Details</h3>
<div *ngIf="country">
  <p><b>Id:</b> {{country.countryId}},  
  <b>Name:</b> {{country.name}} </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 { switchMap } from 'rxjs/operators';
import { CountryService } from '../../services/country.service';
import { Country } from '../../country';

@Component({
    templateUrl: './country.detail.component.html'
})
export class CountryDetailComponent implements OnInit {
    country = {} as Country | undefined;
    constructor(
        private countryService: CountryService,
        private route: ActivatedRoute) { }
    ngOnInit() {
        this.route.params.pipe(
            switchMap((params: Params) => this.countryService.getCountry(params['country-id']))
        ).subscribe(country => this.country = country);
    }
} 
Find the code for person feature.
person-detail.resolver.ts
import { Injectable } from '@angular/core';
import {
  Router, Resolve, RouterStateSnapshot,
  ActivatedRouteSnapshot
} from '@angular/router';
import { PersonService } from './services/person.service';
import { Person } from './person';

@Injectable()
export class PersonDetailResolver implements Resolve<Person | null> {
  constructor(private personService: PersonService, private router: Router) { }

  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Person | null> {
    let id = route.paramMap.get('person-id') ?? '';
    console.log('Resolving for person id:' + id);

    return this.personService.getPerson(id).then(person => {
      if (person) {
        return person;
      } else {
        this.router.navigate(['/person/personList']);
        return null;
      }
    });
  }
} 
person.service.ts
import { Injectable } from '@angular/core';
import { Person } from '../person';

const PERSONS: Person[] = [
  { "personId": "1", "name": "Mahesh", "city": "Varanasi" },
  { "personId": "2", "name": "Ram", "city": "Ayodhya" }
];
let personsPromise = Promise.resolve(PERSONS);
let personsToDisplayPromise =
  Promise.resolve(PERSONS.concat({ "personId": "3", "name": "Krishn", "city": "Mathura" }));

@Injectable()
export class PersonService {
  getPersons(): Promise<Person[]> {
    return personsToDisplayPromise;
  }
  getPerson(id: string): Promise<Person | undefined> {
    return personsPromise
      .then(persons => persons.find(person => person.personId === id));
  }
} 
person.ts
export interface Person { 
  personId: string;
  name: string;
  city: string;
} 
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 { PersonDetailComponent }  from './person-list/detail/person.detail.component';
import { PersonDetailResolver } from './person-detail.resolver';

const personRoutes: Routes = [
	{ 
	  path: '',
          component: PersonComponent,
	  children: [ 
	    {
	      path: 'personList',
	      component: PersonListComponent,
	      children: [
	        {
	   	  path: 'detail/:person-id',
		  component: PersonDetailComponent,
		  resolve: {
		    personDetail: PersonDetailResolver
  		  }
		}
	      ]			
	     }
	   ]
	 }  
];

@NgModule({
        imports: [ RouterModule.forChild(personRoutes) ],
	exports: [ RouterModule ],
	providers: [ PersonDetailResolver ]
})
export class PersonRoutingModule { } 
person.module.ts
import { NgModule }   from '@angular/core';
import { CommonModule }   from '@angular/common';
import { PersonComponent }  from './person.component';
import { PersonListComponent }  from './person-list/person.list.component';
import { PersonDetailComponent }  from './person-list/detail/person.detail.component';
import { PersonService } from './services/person.service';
import { PersonRoutingModule }  from './person-routing.module';

@NgModule({
  imports: [     
       CommonModule,
       PersonRoutingModule
  ], 
  declarations: [
       PersonComponent,
       PersonListComponent,
       PersonDetailComponent
  ],
  providers: [ PersonService ]
})
export class PersonModule { } 
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.list.component.html
<h3>Person List</h3>
<div *ngFor="let person of persons | async" ngClass= "sub-child-menu">
  <p>{{person.name}} | 
  <a [routerLink]="['detail', person.personId]" routerLinkActive="active">Detail</a>
</div>
<div ngClass= "sub-child-container">
   <router-outlet></router-outlet>  
</div> 
person.list.component.ts
import { Component, OnInit } from '@angular/core';
import { PersonService } from '../services/person.service';
import { Person } from '../person';

@Component({
  templateUrl: './person.list.component.html'
})
export class PersonListComponent implements OnInit {
  persons: Promise<Person[]>;
  constructor(private personService: PersonService) {
    this.persons = this.personService.getPersons();
  }
  ngOnInit() {
  }
} 
person.detail.component.html
<h3>Person Details</h3>
<div *ngIf="person">
    <p><b>Id:</b> {{person.personId}},  
    <b>Name:</b> {{person.name}} </p>
    <p><b>City:</b> {{person.city}} </p>
</div> 
person.detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { switchMap } from 'rxjs/operators';
import { PersonService } from '../../services/person.service';
import { Person } from '../../person';

@Component({
  templateUrl: './person.detail.component.html'
})
export class PersonDetailComponent implements OnInit {
  person = {} as Person | undefined;
  constructor(
    private personService: PersonService,
    private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.params.pipe(
      switchMap((params: Params) => this.personService.getPerson(params['person-id']))
    ).subscribe(person => this.person = person);
  }
} 
Now find the code for other files used in the example.
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/countryList" routerLinkActive="active">Country</a></li>
		<li><a routerLink="/person/personList" routerLinkActive="active">Person</a></li>
	     </ul> 
	   </nav>  
		
	   <router-outlet></router-outlet>	
  `
})
export class AppComponent { 
} 
app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PageNotFoundComponent } from './page-not-found.component';

const routes: Routes = [
	{
		path: '',
		redirectTo: '/country/countryList',
		pathMatch: 'full'
	},
	{
		path: 'country',
		loadChildren: () => import('./country/country.module').then(m => m.CountryModule)
	},
	{
		path: 'person',
		loadChildren: () => import('./person/person.module').then(m => m.PersonModule)
	},
	{
		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 { PageNotFoundComponent }  from './page-not-found.component';
import { AppRoutingModule }  from './app-routing.module';

@NgModule({
  imports: [     
    BrowserModule,
    AppRoutingModule,
  ],
  declarations: [
    AppComponent,
    PageNotFoundComponent
  ],
  providers: [ 
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { } 
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;
}
.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;
} 

Run Application

To run the application, find following steps.
1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Run ng serve using command prompt.
4. Now access the URL http://localhost:4200
Angular Resolve Guard Example
In our data store there is the detail available for the country India and China but not for UK. So when we click on detail button for India and China, we will go to country detail route and will see corresponding country detail. But when we click on detail button for UK, we remain on same route. This is because of Angular Resolve guard.

References

Resolve Interface
Common Routing Tasks

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI









©2023 concretepage.com | Privacy Policy | Contact Us