1

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.

Tomas
  • 3,269
  • 3
  • 29
  • 48

3 Answers3

3

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.

Elias Garcia
  • 6,772
  • 11
  • 34
  • 62
  • may you pass link to Angular docs part where `takeUntil` is used and recommended? TBH I'v never seen this in docs, while the solution is god-like! – Tomas Mar 25 '19 at 11:15
  • Sorry! I want to say in RxJs, not in Angular! You can read this post RxJs: Don't Unsubscribe (https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87), which has been written by Ben Lesh, a member of the RxJs Core Team. Hope it helps! – Elias Garcia Mar 25 '19 at 11:18
  • I believe that `async` pipe may works this way. Very interesting, thanks for hint and sources. Even through your answer if perfectly valid & complete, won't you get mad if I'd accept @Fusselreiter answer as gesture of support to new contributor? – Tomas Mar 25 '19 at 11:54
  • The async pipe manages the subscriptions internally using `.unsubscribe()`. You can have a look of the code [here](https://github.com/angular/angular/blob/master/packages/common/src/pipes/async_pipe.ts) It's more complex than using `takeUntil` :) – Elias Garcia Mar 25 '19 at 12:00
2

You can use takeUntil:

import { OnDestroy, OnInit } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';


export class MyComponent implements OnInit, OnDestroy {

 private readonly ngUnSub: Subject<void>;


constructor() {
  this.ngUnSub = new Subject<void>();
}

public ngOnInit(): void {

  if (condition1) {
   this.service1.pipe(takeUntil(this.ngUnSub)).subscribe();
  }

  if (condition2) {
   this.service2.pipe(takeUntil(this.ngUnSub)).subscribe();
  }
}

public ngOnDestroy(): void {
  this.ngUnSub.next();
  this.ngUnSub.complete();
}
1

You can do this:

import { Subscription } from 'rxjs';

...

export class MyComponent implements OnInit, OnDestroy {

    private subscriptions: Subscription[] = [];
  
    ...
  
  
    ngOnDestroy() {
        this.subscriptions.forEach(sub => sub.unsubscribe());
    }

    ....

    if(/* condition 1 */) {
        this.subscriptions.push(this.service1().subscribe());
    }

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

}
  • This case is basically same as I presented in example as array-based one :) But thanks for contributing! – Tomas Mar 25 '19 at 11:02