Angular Resolve Guard Example
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.
Contents
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 AngularResolve
interface. Find the Resolve
interface structure from Angular doc.
interface Resolve<T> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T }
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 useResolve
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; } })); } }
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)) ); }
providers
metadata of @NgModule
decorator as following.
@NgModule({ providers: [ CountryDetailResolver ], ------ }) export class CountryRoutingModule { }
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 useResolve
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; } }); } }
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)); }
providers
metadata of @NgModule
decorator as following.
@NgModule({ providers: [ PersonDetailResolver ], ------ }) export class PersonRoutingModule { }
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
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; } })); } }
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)) ); } }
export interface Country { countryId: string; name: string; capital: string; currency: string; }
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 { }
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 { }
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 { }
<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>
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() { } }
<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>
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); } }
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; } }); } }
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)); } }
export interface Person { personId: string; name: string; city: string; }
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 { }
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 { }
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 { }
<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>
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() { } }
<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>
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); } }
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(); } }
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 { }
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 { }
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 { }
.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

References
Resolve InterfaceCommon Routing Tasks