Best Way To Implement Angular Router Resolver
In this article, I will implement an angular router resolver with an explanation. So we will create two component product list and production description. Product List will have a product list resolver and the other will be a product description resolver which takes the product slug from the current route param.
So what is an angular resolver?
Angular resolver is an injectable service class that gives pre-data before component init. Resolver used for pre-fetching data after angular route end.
So how is an angular resolver implemented?
An angular resolver implements by Implementing an angular Resolve interface. and angular Resolve takes two parameters, ActivatedRouteSnapshot, and other RouterStateSnapshot
export declare interface Resolve<T> T;
Note: In this tutorial, we will make some HTTP calls. So we will use storerestapi prototype, Store Rest Api provide fake store data
Let’s start the angular resolver tutorial
Step 01. Generate an angular project and set up
A new angular project is generated with SCSS style and with @angular/router, You can generate other style options like CSS or LESS. Actually, I always use SCSS for styling.
ng new angular-router-resolver --style=scss --routing
Go to project directory angular-router-resolver
cd angular-router-resolver
Edit the app.component.html file to add router-outlet for multiple routes
<h1>This Is Angular Router Resolver</h1> <ul> <li><a [routerLink]="'/'">Home</a></li> <li><a [routerLink]="'/products'">Products</a></li> </ul> <router-outlet></router-outlet>
Step 02. Generate some necessary router component
Generate ProductList and ProductDescription Component.
ng g c product/productList ng g c product/productDescription
add product list and product description to our app routing, In th app-routing.module.ts file
import NgModule from '@angular/core'; import RouterModule, Routes from '@angular/router'; import ProductDescriptionComponent from './product/product-description/product-description.component'; import ProductListComponent from './product/product-list/product-list.component'; const routes: Routes = [ path: 'products', pathMatch: 'full', component: ProductListComponent , path: 'products/:slug', component: ProductDescriptionComponent ]; @NgModule( imports: [RouterModule.forRoot(routes)], exports: [RouterModule] ) export class AppRoutingModule
Step 03. Generate Product Service and implement product list and product description HTTP methods
Generate Product Service inside the product directory
ng g s product/product --skip-tests
edit the product.service.ts file, and implement getProducts and getProduct methods
import Injectable from '@angular/core'; import HttpClient from '@angular/common/http'; import Observable from 'rxjs'; @Injectable( providedIn: 'root' ) export class ProductService baseUrl: string = 'https://api.storerestapi.com/v1' constructor(private http: HttpClient) getProducts(): Observable<any> return this.http.get(this.baseUrl + '/products') getProduct(slug: string): Observable<any> return this.http.get(this.baseUrl + '/products/' + slug)
Add HttpClientModule for use of HttpClient class. In the app.module.ts file
import NgModule from '@angular/core'; import BrowserModule from '@angular/platform-browser'; import HttpClientModule from '@angular/common/http'; ... @NgModule( ... imports: [ ... HttpClientModule ], ... ) export class AppModule
Step 04. Implement product-list resolver
Generate product-list resolver inside product/resolver directory
ng g resolver product/resolver/product-list --skip-tests
Implement ProductListResolver class to return product list observable
import Injectable from '@angular/core'; import Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot from '@angular/router'; import map, Observable, of from 'rxjs'; import ProductService from '../product.service'; @Injectable( providedIn: 'root' ) export class ProductListResolver implements Resolve<any> constructor(private productService: ProductService) resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> return this.productService.getProducts().pipe(map(res => res?.data));
Add Product-list resolver to ProductList route
In the app-routing.component.ts file.
... import ProductListResolver from './product/resolver/product-list.resolver'; const routes: Routes = [ path: 'products', component: ProductListComponent, pathMatch: 'full', resolve: products: ProductListResolver , ... ]; @NgModule( imports: [RouterModule.forRoot(routes)], exports: [RouterModule] ) export class AppRoutingModule
Step 05. Access Product List data from ProductList Component
import Component, OnInit from '@angular/core'; import ActivatedRoute from '@angular/router'; @Component( selector: 'app-product-list', templateUrl: './product-list.component.html', styleUrls: ['./product-list.component.scss'] ) export class ProductListComponent implements OnInit data: any; constructor(private route: ActivatedRoute) ngOnInit(): void console.log(this.route.snapshot.data?.products) this.data = this.route.snapshot.data;
Render ProductList data to HTML, Edit product/product-list/product-list.component.html file
<h2>Product List</h2> <ul> <li *ngFor="let product of data?.products"> <a [routerLink]="'/products/' + product?.slug">product?.title</a> </li> </ul>
Step 06. Implement product resolver
In the product resolver, we will take the product slug from the route param
So Generate a product solver inside product/resolver directory
ng g resolver product/resolver/product
Implement Product Resolver
import Injectable from '@angular/core'; import Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot from '@angular/router'; import map, Observable, of from 'rxjs'; import ProductService from '../product.service'; @Injectable( providedIn: 'root' ) export class ProductResolver implements Resolve<any> constructor(private productService: ProductService) resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> return this.productService.getProduct(route.params['slug']).pipe(map(res => res?.data));
Add Product Resolve to the product description route
... import ProductResolver from './product/resolver/product.resolver'; const routes: Routes = [ ... path: 'products/:slug', component: ProductDescriptionComponent, resolve: product: ProductResolver ]; ... export class AppRoutingModule
Access Product data from the product description component, In the product-description.component.ts file
import Component, OnInit from '@angular/core'; import ActivatedRoute from '@angular/router'; @Component( selector: 'app-product-description', templateUrl: './product-description.component.html', styleUrls: ['./product-description.component.scss'] ) export class ProductDescriptionComponent implements OnInit product: any; constructor(private route: ActivatedRoute) ngOnInit(): void this.product = this.route.snapshot.data['product']
Render product in product description HTML file
<h1>Product Description</h1> <div *ngIf="product != null"> <h2>Title: product?.title</h2> <h3>Price: product?.price</h3> </div>
Handling Resolver Api Error
You can handle error via RxJs catchError operator,
import Injectable from '@angular/core'; import Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot from '@angular/router'; import catchError, map, Observable, of from 'rxjs'; import ProductService from '../product.service'; @Injectable( providedIn: 'root' ) export class ProductResolver implements Resolve<any> constructor(private productService: ProductService) resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> return this.productService.getProduct(route.params['slug']).pipe( map(res => res?.data), catchError(() => return of("Product is not found!") ) );
In the case of an error, I will show an alert error message and route it to the product list, and for resolver return EMPTY
import Injectable from '@angular/core'; import Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot from '@angular/router'; import catchError, EMPTY, map, Observable, of from 'rxjs'; import ProductService from '../product.service'; @Injectable( providedIn: 'root' ) export class ProductResolver implements Resolve<any> constructor(private productService: ProductService, private router: Router) resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> return this.productService.getProduct(route.params['slug']).pipe( map(res => res?.data), catchError(() => // return of("Product is not found!") alert("Product Not Found") this.router.navigate(['/products']) return EMPTY ) );
Implement Resolver base Loader
import Component, OnInit from '@angular/core'; import ResolveEnd, ResolveStart, Router from '@angular/router'; import filter, mapTo, merge, Observable from 'rxjs'; @Component( selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] ) export class AppComponent implements OnInit title="angular-router-resolver"; // Custom Loader Trigger isLoading$!: Observable<boolean>; constructor(private router: Router) ngOnInit() // Custom Loader Trigger const loaderStart$ = this.router.events.pipe( filter((event) => event instanceof ResolveStart), mapTo(true) ) const loaderEnd$ = this.router.events.pipe( filter((event) => event instanceof ResolveEnd), mapTo(false) ) this.isLoading$ = merge(loaderStart$, loaderEnd$)
Add loader in HTML
<svg *ngIf="isLoading$ | async" width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#000"> <g fill="none" fill-rule="evenodd"> <g transform="translate(1 1)" stroke-width="2"> <circle stroke-opacity=".5" cx="18" cy="18" r="18"/> <path d="M36 18c0-9.94-8.06-18-18-18"> <animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="1s" repeatCount="indefinite"/> </path> </g> </g> </svg>