Resolve Guard In Angular
Before we go to a Route, we can use the Angular Resolve Guard or Angular Resolvers to load some data. In this article, we will learn what Angular Resolve guard is and how to use it in an Angular app.
Angular Resolve Guard
When we navigate to a route, Angular renders the Angular Component. The component will then make an HTTP call to the back-end server in order to retrieve data and present it to the user. This is usually done in the ngOnInit Life cycle hook.
The disadvantage of the above method is that the user will see an empty component. The component displays the data after it has arrived. One solution is to display some sort of loading indicator.
Another option is to employ the Resolve Guard to remedy the problem. Before traveling to the route, the Resolve Guard pre-fetches the data. As a result, the data and the component are rendered together.
How to Use Resolve Guard
We'll start by creating an Angular Service that implements the Resolve Interface.
The resolve method must be implemented by the service. A resolve method must either return an Observable<any>, a Promise<any>, or simply data. The Resolve interface has the following interface signature.
interface Resolve<T> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<T> | Promise<T> | T }
import { Injectable } from '@angular/core'; import { Resolve, ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router'; import { ProductService } from './product.service'; import { Observable, of } from 'rxjs'; @Injectable() export class ProductListResolveService implements Resolve<any>{ constructor(private _router:Router , private productService:ProductService ) { } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> { return this.productService.getProducts(); } }
providers: [ProductListResolveGuardService]
Following the creation of the resolver, we must edit the route specification and include the resolve property as seen below.
{ path: 'product', component: ProductComponent, resolve: {products: ProductListResolveService} },
constructor(private route: ActivatedRoute){ } ngOnInit() { this.products=this.route.snapshot.data['products']; }
Canceling Navigation
Multiple Resolvers
{ path: 'product', component: ProductComponent, resolve: {products: ProductListResolveService, , data:SomeOtherResolverService} }
Resolve Guard Example
import {Product} from './Product' import { of, Observable, throwError} from 'rxjs'; import { delay, map } from 'rxjs/internal/operators'; export class ProductService{ products: Product[]; public constructor() { this.products=[ new Product(1,'Memory Card',500), new Product(2,'Pen Drive',750), new Product(3,'Power Bank',100), new Product(4,'Computer',100), new Product(5,'Laptop',100), new Product(6,'Printer',100), ] } //Return Products List with a delay public getProducts(): Observable<Product[]> { return of(this.products).pipe(delay(1500)) ; } // Returning Error // This wil stop the route from getting Activated //public getProducts(): Observable<Product[]> { // return of(this.products).pipe(delay(1500), map( data => { // throw throwError("errors occurred") ; // })) //} public getProduct(id): Observable<Product> { var Product= this.products.find(i => i.productID==id) return of(Product).pipe(delay(1500)) ; } }
Add the following code under the product.ts file:
export class Product { constructor(productID:number, name: string , price:number) { this.productID=productID; this.name=name; this.price=price; } productID:number ; name: string ; price:number; }
Product Component without resolver
import { Component, OnInit } from '@angular/core'; import { ProductService } from './product.service'; import { Product } from './product'; import { ActivatedRoute } from '@angular/router'; @Component({ templateUrl: './product1.component.html', }) export class Product1Component { public products:Product[]; constructor(private route: ActivatedRoute,private productService:ProductService){ } ngOnInit() { this.productService.getProducts().subscribe(data => { this.products=data; }); } }
Resolve Guard
import { Injectable } from '@angular/core'; import { Resolve, ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router'; import { ProductService } from './product.service'; import { Observable, of } from 'rxjs'; import { Product } from './Product'; @Injectable() export class ProductListResolverService implements Resolve<Product>{ constructor(private productService:ProductService ) { } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> { console.log("ProductListResover is called"); return this.productService.getProducts(); } }
Product Component with resolver
import { Component, OnInit } from '@angular/core'; import { ProductService } from './product.service'; import { Product } from './product'; import { ActivatedRoute } from '@angular/router'; @Component({ templateUrl: './product2.component.html', }) export class Product2Component { public products:Product[]; constructor(private route: ActivatedRoute,private productService:ProductService){ } ngOnInit() { this.products=this.route.snapshot.data['products']; } }
The resolve guard must be added to the route definition in the app.routing.module:
export const appRoutes: Routes = [ { path: 'home', component: HomeComponent }, { path: 'contact', component: ContactComponent }, { path: 'product1', component: Product1Component }, { path: 'product2', component: Product2Component, resolve: {products: ProductListResolverService} } ]
In AppModule, add the ProductListResolverService to the Providers array.
providers: [ProductService,ProductListResolverService],
ProductDetail Component
import { Injectable } from '@angular/core'; import { Router, Resolve, ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router'; import { ProductService } from './product.service'; import { Observable, of } from 'rxjs'; import { catchError, map } from 'rxjs/internal/operators'; import { Product } from './Product'; @Injectable() export class ProductResolverService implements Resolve<any>{ constructor(private router:Router , private productService:ProductService ) { } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any { let id = route.paramMap.get('id'); console.log("ProductResolverService called with "+id); return this.productService.getProduct(id) .pipe(map( data => { if (data) { console.log(data); return data; } else { console.log('redirecting'); this.router.navigate(['/product2']); return null } })) } }
Add the following code under the product2-detail.component.ts file:
import { Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Product } from './product'; @Component({ templateUrl: './product2-detail.component.html', }) export class Product2DetailComponent { product:Product; constructor(private _Activatedroute:ActivatedRoute){ this.product=this._Activatedroute.snapshot.data['product']; } }
import { BrowserModule } from '@angular/platform-browser'; import { NgModule, ErrorHandler } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { HomeComponent} from './home.component' import { ContactComponent} from './contact.component' import { ProductService } from './product.service'; import { ProductListResolverService } from './product-list-resolver.service'; import { ProductResolverService } from './product-resolver.service'; import { AppRoutingModule } from './app-routing.module'; import { Product1Component } from './product1.component'; import { Product1DetailComponent} from './product1-detail.component' import { Product2Component } from './product2.component'; import { Product2DetailComponent } from './product2-detail.component'; @NgModule({ declarations: [ AppComponent,HomeComponent,ContactComponent,Product1Component, Product2Component,Product1DetailComponent, Product2DetailComponent ], imports: [ BrowserModule, FormsModule, HttpModule, RouterModule, AppRoutingModule ], providers: [ProductService,ProductListResolverService,ProductResolverService,], bootstrap: [AppComponent] }) export class AppModule { }
Add the following code under the app.component.ts file:
import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'Routing Module - Route Guards Demo'; } app.component.html <div class="container"> <nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" [routerLink]="['/']"><strong> {{title}} </strong></a> </div> <ul class="nav navbar-nav"> <li><a [routerLink]="['home']">Home</a></li> <li><a [routerLink]="['product1']">Product1</a></li> <li><a [routerLink]="['product2']">Product2</a></li> <li><a [routerLink]="['contact']">Contact us</a></li> </ul> </div> </nav> <router-outlet></router-outlet> </div>
Add the following code under the app.routing.module.ts file:
import { NgModule} from '@angular/core'; import { Routes,RouterModule } from '@angular/router'; import { HomeComponent} from './home.component' import { ContactComponent} from './contact.component' import { Product1Component} from './product1.component' import { Product2Component} from './product2.component' import { Product1DetailComponent} from './product1-detail.component' import { ProductListResolverService } from './product-list-resolver.service'; import { Product2DetailComponent } from './product2-detail.component'; import { ProductResolverService } from './product-resolver.service'; export const appRoutes: Routes = [ { path: 'home', component: HomeComponent }, { path: 'contact', component: ContactComponent }, { path: 'product1', component: Product1Component }, { path: 'product2', component: Product2Component, resolve: {products: ProductListResolverService} }, { path: 'product1/:id', component: Product1DetailComponent }, { path: 'product2/:id', component: Product2DetailComponent, resolve:{product:ProductResolverService} }, { path: '', redirectTo: 'home', pathMatch: 'full' }, ]; @NgModule({ declarations: [ ], imports: [ RouterModule.forRoot(appRoutes) ], providers: [], bootstrap: [] }) export class AppRoutingModule { }
Add the following code under the contact.component.ts file:
import {Component} from '@angular/core'; @Component({ template: `<h1>Contact Us</h1> <p>TekarticlesHub </p> ` }) export class ContactComponent { }
Add the following code under the home.components.ts file:
import {Component} from '@angular/core'; @Component({ template: `<h1>Welcome!</h1> <p>This is Home Component </p> ` }) export class HomeComponent { }
Add the following code under the product.ts file:
export class Product { constructor(productID:number, name: string , price:number) { this.productID=productID; this.name=name; this.price=price; } productID:number ; name: string ; price:number; }
Add the following code under the product.service.ts file:
import {Product} from './Product' import { of, Observable, throwError} from 'rxjs'; import { delay, map } from 'rxjs/internal/operators'; export class ProductService{ products: Product[]; public constructor() { this.products=[ new Product(1,'Memory Card',500), new Product(2,'Pen Drive',750), new Product(3,'Power Bank',100), new Product(4,'Computer',100), new Product(5,'Laptop',100), new Product(6,'Printer',100), ] } //Return Products List with a delay public getProducts(): Observable<Product[]> { return of(this.products).pipe(delay(1500)) ; } // Returning Error // This wil stop the route from getting Activated //public getProducts(): Observable<any> { // return of(this.products).pipe(delay(3000), map( data => { // throw throwError("errors occurred") ; // })) //} public getProduct(id): Observable<Product> { var Product= this.products.find(i => i.productID==id) return of(Product).pipe(delay(1500)) ; } }
Add the following code under the product-list-resolver.ts file:
import { Injectable } from '@angular/core'; import { Router, Resolve, ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router'; import { ProductService } from './product.service'; import { Observable, of } from 'rxjs'; @Injectable() export class ProductListResolverService implements Resolve<any>{ constructor(private productService:ProductService ) { } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> { console.log("ProductListResover is called"); return this.productService.getProducts(); } }
Add the following code under the product-resolver.service.ts file:
import { Injectable } from '@angular/core'; import { Router, Resolve, ActivatedRouteSnapshot,RouterStateSnapshot } from '@angular/router'; import { ProductService } from './product.service'; import { Observable, of } from 'rxjs'; import { catchError, map } from 'rxjs/internal/operators'; import { Product } from './Product'; @Injectable() export class ProductResolverService implements Resolve<any>{ constructor(private router:Router , private productService:ProductService ) { } resolve1(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> { console.log("ProductResolverService called"); let id = route.paramMap.get('id'); return this.productService.getProduct(id); } resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): any { let id = route.paramMap.get('id'); console.log("ProductResolverService called with "+id); return this.productService.getProduct(id) .pipe(map( data => { if (data) { console.log(data); return data; } else { console.log('redirecting'); this.router.navigate(['/product2']); return null } })) } }
Add the following code under the product1.component.ts file:
import { Component, OnInit } from '@angular/core'; import { ProductService } from './product.service'; import { Product } from './product'; import { ActivatedRoute } from '@angular/router'; @Component({ templateUrl: './product1.component.html', }) export class Product1Component { public products:Product[]; constructor(private route: ActivatedRoute,private productService:ProductService){ } ngOnInit() { console.log('ngOnInit'); this.productService.getProducts().subscribe(data => { this.products=data; }); } }
Add the following code under the product1.component.html file:
<h1> Without Resolve</h1> <div class='table-responsive'> <table class='table'> <thead> <tr> <th>Name</th> <th>Price</th> </tr> </thead> <tbody> <tr *ngFor="let product of products;"> <td><a [routerLink]="['/product1',product.productID]">{{product.name}} </a> </td> <td>{{product.price}}</td> </tr> </tbody> </table> </div>
Add the following code under the product1-detail.component.ts file:
import { Component } from '@angular/core'; import { Router,ActivatedRoute } from '@angular/router'; import { ProductService } from './product.service'; import { Product } from './product'; @Component({ templateUrl: './product1-detail.component.html', }) export class Product1DetailComponent { product:Product; constructor(private _Activatedroute:ActivatedRoute, private _router:Router, private _productService:ProductService){ let id=this._Activatedroute.snapshot.params['id']; console.log(id); this._productService.getProduct(id) .subscribe( data => { this.product=data console.log(this.product); }) } }
Add the following code under the product1-detail.component.html file:
<h1>Product Details Page [Without resolve]</h1> Product : {{ product?.name}} <br> Price : {{product?.price}}
Add the following code under the product2.component.ts file.
import { Component, OnInit } from '@angular/core'; import { ProductService } from './product.service'; import { Product } from './product'; import { ActivatedRoute } from '@angular/router'; @Component({ templateUrl: './product2.component.html', }) export class Product2Component { public products:Product[]; constructor(private route: ActivatedRoute,private productService:ProductService){ } ngOnInit() { this.products=this.route.snapshot.data['products']; } }
Add the following code under the product2.component.html file:
<h1> With Resolve</h1> <div class='table-responsive'> <table class='table'> <thead> <tr> <th>Name</th> <th>Price</th> </tr> </thead> <tbody> <tr *ngFor="let product of products;"> <td><a [routerLink]="['/product2',product.productID]">{{product.name}} </a> </td> <td>{{product.price}}</td> </tr> </tbody> </table> </div>
Add the following code under the product2-detail.component.ts file:
import { Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Product } from './product'; @Component({ templateUrl: './product2-detail.component.html', }) export class Product2DetailComponent { product:Product; constructor(private _Activatedroute:ActivatedRoute){ this.product=this._Activatedroute.snapshot.data['product']; } }
Add the following code under the product2-detail.component.html file:
<h1>Product Details Page [With resolve]</h1> Product : {{ product?.name}} <br> Price : {{product?.price}}