angularobservable

How to use an Observable of an Observable object in Angular 18


I have a beginner Angular project and I want to load a component where I get the ID of a record from the routerparams and from that I do an api call to my backend to get the data. The issue is that my return is Observable<Observable> and I'm not sure how to use this to get the data from the Product with that ID.

import { Component, Input, OnInit } from '@angular/core';
import { Product, ProductsService } from './products.service';
import { map, Observable } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-product-view',
  standalone: true,
  imports: [CommonModule],
  template: `
    
      product-view works!
      <br />
      
    <article class="product-panel" *ngIf="(product$ | async) as product">
            {{product.id}}
             //ABOVE CODE THROWS ERROR
             //NOTE I WANT TO GET PRODUCT DATA BUT IT IS OBSERVABLE<Product>
    </article>
  `,
  styles: ``
})

export class ProductViewComponent implements OnInit{
  
  constructor(private productsService: ProductsService, private route: ActivatedRoute){}
  
  product$: Observable<Observable<Product>>;
  productId: string;

  ngOnInit(): void {
    this.product$ = this.route.paramMap.pipe(
      map(params => {
      return this.productsService.getProduct(params.get('id')!);
      })
    )
  }
}

Here is my service:

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

export interface Product {
  id: string;
  name: string;
  category: string;
  subCategory: string;
}

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

  readonly URL = "http://localhost:5999/products";

  constructor(private http: HttpClient) { 
    this.getProducts();
  }
  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(this.URL); 
  }
  getProduct(id:string): Observable<Product> {
    return this.http.get<Product>(this.URL + "/" + id);
  }
}

I could get the router param in the way shown below but I kind of like the way I set it up because if the id in the routeparams changes it will do another api call.

   this.productId = this.route.snapshot.params['id'];
   this.product$ = this.productsService.getProduct(this.productId);

(Note: for this method I would change the this.product$ to be of type Observable instead of Observable<Observable> )


Solution

  • Use switchMap (that transform an Observable in another observable) instead of map to return a simple Observable

    //see that product$ is an Observable of Product
    product$!:Observable<Product>
    
    this.product$ = this.route.paramMap.pipe(
      switchMap (params => {
        return this.productsService.getProduct(params.get('id')!);
      })
    )