angularmemory-leaksrxjs-observablesspartacus-storefront

Refactor Nested Subscriptions properly for Event Listening


I have a special situation where the usual usage of "switchMap" to avoid nested subscriptions just doesn't cut it.

I discovered this code, that uses nested subscriptions, and wanted to refactor it.

this.eventService.get(ProductDetailsPageEvent)
  .pipe(takeUntil(this.destroy$))
  .subscribe((event) => {
    combineLatest([
      this.getCurrentProduct(),
      this.getCurrentPage(),
    ])
      .pipe(first())
      .subscribe(([product, page]) => {
        track(product,page,event);
      });
  });

This code makes sure that some tracking operation is triggered everytime (and only then!) whenever the user navigates to a Product Detail Page (ProductDetailsPageEvent is fired).

I wanted to refactor this code and tried this to avoid nested subscriptions

this.eventService.get(ProductDetailsPageEvent)
  .pipe(
    takeUntil(this.destroy$),
    switchMap((event) =>
      combineLatest([
        of(event),
        this.getCurrentProduct(),
        this.getCurrentPage(),
      ])
    ),
    first()
  ).subscribe(([event, page, product]) =>{
      track(product,page,event);
    }
  );

It works fine for the first time (note the "first()"-chain operator), but every other attempt to access the PDP is no longer triggering the "track"-method. If I remove the "first()"-operator I have the problem, that the "track" method is triggered even when I leave the PDP, resulting in console errors because there is no current product to get...

How do solve this delimma without resorting to nested subscriptions? How do I make sure the page and product Observables are only retrieved on every new PDP-event fired?


Solution

  • Your code works great, but we need to unsubscribe the subscription, when the component is destroyed. Else it will keep listening for events and gradually pile up during application use, finally causing a memory leak. If you do this, there is no need for first operator.

    As a best practice always add your subscribes to a subscription and unsubscribe on component destroy.

    private sub = new Subscription();
    
    this.sub.add(
     this.eventService.get(ProductDetailsPageEvent)
      .pipe(
        switchMap((event) =>
          combineLatest([
            of(event),
            this.getCurrentProduct(),
            this.getCurrentPage(),
          ])
        ),
      ).subscribe(([event, page, product]) =>{
          track(product,page,event);
        }
      )
    );
    ...
    
    ...
    ngOnDestroy() { 
        this.sub.unsubscribe();
    }