1

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?

1 Answer 1

0

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();
}
9
  • Thanks for your comment. I'll try it out, but I always thought that "takeUntil(this.destroy$)" takes care of unsubscribing. Like you can either unsubscribe or add this takeUntil parameter instead. Note that this event Listener is not part of a component, but a service on its own triggered on app start Commented Jul 5 at 8:45
  • @SergejBjakow sorry I missed the takeUntil part, but what you mentioned might help, instead of subscribing inside the service return the observable in the service method. Then subscribe to the observable in the component, along with this subscription, we can remove take until for this particular scenario, my understanding is this.destroyed$ is not reset when component is destroyed, mostly keep the subscriptions at the component level, the service returns observables for the component to subscribe Commented Jul 5 at 8:47
  • Note that the entire logic is part of a Service (provided in root) and NOT a component private destroy$ = new Subject(); ngOnDestroy(): void { this.destroy$.next(null); } I'm not sure if that makes even sense within a service, so maybe there is even more to refactor... Commented Jul 5 at 9:57
  • @SergejBjakow The service if its providedIn: 'root' is never destroyed until you refresh the page. If the service is added to the providers array of a component or module. it will be destroyed only when the parent component/component is destroyed. So keep this mind and check if the service is destroyed when the component is destroyed, so this.destroy$.next(null) not being called is the problem. Commented Jul 5 at 10:00
  • 1
    @SergejBjakow When you are to providers array, ngOnDestroy is called. Try this approach from now onwards. This will give you clean code Commented Jul 5 at 13:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.