Tech Incent
Angular

Implement angular (product) details page with reactive service

angular-reactive-service-preview

In this article, I am implementing product details based on the product_slug route param. I will implement a reactive service method that is dependent on route params observable, So what does it mean? That’s mean when the parameter change by another product_slug the service trigger another API call with a changed product_slug

Note: in this guide, I am going to use fake rest API data from https://storerestapi.com product data.

Let’s prepare a fresh project

start a new project and create a product base feature module that will have a product page and product details (that’s details page is a reactive dynamic route )

ng new angular-reactive-service

Generate product module and product service(that’s service will have product details reactive method). Also, generate a product list and product details component.

ng g m product --routing
ng g s product/product --skip-tests
ng g c product/product-list
ng g c product/product-details

Link product list and product details page in product routing module. In the product.routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductDetailsComponent } from './product-details/product-details.component';
import { ProductListComponent } from './product-list/product-list.component';

const routes: Routes = [
  {
    path: '',
    component: ProductListComponent,
  },
  {
    path: ':slug',
    component: ProductDetailsComponent
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class ProductRoutingModule { }

Link product module in the project, in the app-routing.module.ts file

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
  {
    path: '',
    loadChildren: () => import('./product/product.module').then(m => m.ProductModule)
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Make sure cleanup app.component.html file

<router-outlet></router-outlet>

import HTTP module

In the example, I am going to make some HTTP call. So angular require HttpClientModule for make HTTP calls. So import and export HttpClientModule in app.module.ts file

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  exports: [
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Let’s implement product service

In the product.service.ts file, I implement two methods getProducts and getProduct. Both methods make HTTP call

getProducts() method: getProducts is to create an HTTP call for a list of products.

getProduct() method: the getProduct method is also an HTTP call for single product details which is based on the product slug. getProduct method is one of our reactive service methods. which is take Obserable of productSlug. And this productSlug comes through the current route param

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, switchMap } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class ProductService {

  constructor(private http: HttpClient) { }
  
  getProducts(): Observable<any> {
    return this.http.get('https://api.storerestapi.com/products')
  }

  getProduct(productSlug: Observable<string>): Observable<any> {
    return productSlug.pipe(
      switchMap((slug) => {
        return this.http.get(`https://api.storerestapi.com/products/${slug}`)
      })
    )
  }
}

Implement product list component

Edit product-list.component.ts file

products$: Product property container list of product list from HTTP call when be component initialize.

import { Component, OnInit } from '@angular/core';
import { map, Observable } from 'rxjs';
import { ProductService } from '../product.service';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit {
  products$!: Observable<any[]>;
  
  constructor(private productService: ProductService) { }

  ngOnInit(): void {
    this.products$ = this.productService.getProducts().pipe(map(res => res?.data))
  }

}

Edit product-list.component.ts file

<ul *ngIf="(products$ | async) as products;else empty">
  <li *ngFor="let product of products">
    <a [routerLink]="['/' + product?.slug]">{{ product?.title }}</a>
  </li>
</ul>
<ng-template #empty>
  <h3>Loading ...</h3>
</ng-template>

Implement product details (reactive page)

In the product details component, I will show the product list which was the product-list component. and I will show route base product details in the same component

Edit product-details.component.ts file

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map, Observable } from 'rxjs';
import { ProductService } from '../product.service';

@Component({
  selector: 'app-product-details',
  templateUrl: './product-details.component.html',
  styleUrls: ['./product-details.component.scss']
})
export class ProductDetailsComponent implements OnInit {
  
  product$!: Observable<any>
  
  constructor(private route: ActivatedRoute, private productService: ProductService) { }

  ngOnInit(): void {
    const productSlug$ = this.route.params.pipe(map((params: any) => params?.["slug"]))
    this.product$ = this.productService.getProduct(productSlug$).pipe(map(res => res?.data))
  }

}

Edit product-details-component.html file

<app-product-list> element component render list of product. And in the right, product details will be rendered

<div 
  *ngIf="(product$ | async) as productObj;else empty" style="display: grid;grid-template-columns: repeat(3, minmax(0, 1fr));">
  // Product list component
  <app-product-list></app-product-list>

  // Product details
  <div style="grid-column: span 2/span 2;">
    <h1>{{productObj?.title}}</h1>
    <hr>
    <p><strong>Price:</strong> {{productObj?.price}}</p>
    <p><strong>Created At:</strong> {{productObj?.createdAt | date:"medium"}}</p>
    <p><strong>Created By:</strong> {{productObj?.createdBy?.name}}</p>
    <p><strong>Category:</strong> {{productObj?.category?.name}}</p>
  </div>
</div>

<ng-template #empty>
  <h3>Loading ...</h3>
</ng-template>

Checkout code: https://github.com/techincent/angular-reactive-service

Related posts

Angular Date as ago (minutes / hours / days / months / years ago) pipe

Sajal Mia

Explained RxJs switchMap operator with example

Sajal Mia

Angular JWT token authentication (login, auth and error interceptors, guard, protected route) example: Step by step

Sajal Mia

Angular Production-Ready Project Setup

Sajal Mia

Angular lifecycle hooks explanation

Sajal Mia

How to add bootstrap 5 in the angular application?

Sajal Mia