angularrxjs

Why is my RxJs Subject not working as expected?


ProductGalleryComponent.html iterates over products. The click event of each image calls the setSelectedProduct function passing to it the product that was clicked.

<div class="row py-3 px-2" style="background: #f5f6f8">
  <div
    class="col d-flex flex-column col-12 col-md-4 col-lg-3 mb-4 gallery-img-col"
    *ngFor="let product of products"
  >
    <img
      class="gallery-img"
      src="assets/img/{{ product.images[0] }}"
      (click)="setSelectedProduct(product)"
    />
  </div>
</div>

The setSelectedProduct function of ProductGalleryComponent.ts calls the setSelectedPRoduct function of the ProductGalleryService, passing the selected product. It then navigates to the SelectedProductComponent.

public setSelectedProduct = (product: ProductModel) => {
    this.productGalleryService.setSelectedProduct(product);
    this.router.navigateByUrl('/selected-product');
  }

ProductGalleryService declares a subject and an observable of the subject. In the setSelecedProduct function, it calls the next function of the subject, passing to it the selected product:

export class ProductGalleryService {
    public selectedProductSubject = new Subject<ProductModel>();
    selectedProductSubject$ = this.selectedProductSubject.asObservable();

    public setSelectedProduct = (product: ProductModel) => {
        this.selectedProductSubject.next(product);
    }
}

The SelectedProductComponent subscribes to the selectedProductSubject of ProductGalleryService via the constructor. It is expected to receive the product broadcast by ProductGalleryService.selectedProductSubject but the product is never received.

export class SelectedProductComponent {        
    private productGalleryService = inject(ProductGalleryService)

    constructor() {
        this.setSelectedProduct()
    }

  private setSelectedProduct = () => {
    this.productGalleryService.selectedProductSubject$.subscribe((product) => {
      this.selectedProduct = product;
      console.log('selectedProductSubject.selectedProduct', this.selectedProduct)
    })
  }
}

Solution

  • I believe the ProductGalleryService.selectedProductSubject$ subject has already emitted before the SelectedProductComponent subscribes.

    How to get around this problem?

    RxJS offers another type of Subject - a ReplaySubject. When subscribing to a ReplaySubject, it will re-emit any previously pushed values. The ReplaySubject has a buffer size, how many items to store and re-emit. For your use case, I believe you only care about the last one, so BufferSize is 1.

    public selectedProductSubject = new ReplaySubject<ProductModel>(1);

    1. Subject vs BehaviourSubject vs ReplaySubject
    2. How to get current value of RxJS Subject or Observable?