PROGRAMMING LANGUAGES

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>

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button