Search code examples
angularsubscription

Safe unsubscribing to optional controller subscriptions


Is there a confirmed and elegant idiom of unsubscribing to all existing but possibly not initialized subscriptions in controller? Consider following snippet:

/* Somewhere on init */
if(/* condition 1 */) {
  this.subscription1 = this.service1().subscribe();
}

if(/* condition 2 */) {
  this.subscription2 = this.service2().subscribe();
}

And at cleanup phase:

onDestory() {
  try{
    this.subscription1.unsubscribe();
    this.subscription2.unsubscribe();
  } catch e {
    /* One of subscriptions was uninitialized */
    /* If that was first one, second may be left unsubscribed */
  }
}

Basically there are for sure some of straight-forward methods like beloved ifology:

  if(this.subscription1) {
  this.subscription1.unsubscribe();
  }
  if(this.subscription2) {
    this.subscription2.unsubscribe();
  }

of using array of subscriptions instead of controller fields:

/* Somewhere on init */
if(/* condition 1 */) {
  this.subscriptions.push(this.service1().subscribe())
}

if(/* condition 2 */) {
  this.subscriptions.push(this.service2().subscribe())
}

  noDestroy() {
    this.subscriptions.forEach(s => s.unsubscribe();)
  }

But maybe there are some neat and reliable ways of doing such task? According to my experience, ifology is tedious to conserve and easy to forget to update, while array-based makes your subscription list non-menagable in case you wish to selectively unsubscribe to selected subscription in other controller lifecycle moment than onDestroy.


Solution

  • Why not use the takeUntil RxJs operator?

    You can subscribe to the observables you want, waiting for the takeUntil operator to unsubscribe from the Observable chain when another Observable emits:

    You can declare your new Observable as a Subject:

    private destroySubscriptions$ = new Subject<void>();
    
    /* Somewhere on init */
    if(/* condition 1 */) {
      this.service1()
        .pipe(
          takeUntil(this.destroySubscriptions$)
        ).subscribe();
    }
    
    if(/* condition 2 */) {
      this.service2()
        .pipe(
          takeUntil(this.destroySubscriptions$)
        ).subscribe();
    }
    

    And on the cleanup phase (typically on the onDestroy Angular lifecycle hook):

    ngOnDestroy() {
      this.destroySubscriptions$.next();
    }
    

    Also, this is a recommendend pattern in RxJs to destroy your subscriptions! You can read more about the takeUntil operator here.