dmuensterer
dmuensterer

Reputation: 1885

Is there an efficient way to keep track of Subscriptions in rxjs

Let's say I have lots of components containing a structure like this

ngOnInit() {
    this.fb.user$.subscribe(data => {
        if (data != null) {
          //User is logged in
          this.fb.afs.collection('col').doc(data.info).valueChanges().subscribe(info => {
            this.info = info;
          })
          //Lots of other subscriptions following...
        } else {
          //User is logged out
        }
    })
}

Once a user logs out, a Firebase permission exception is thrown because the

this.fb.afs.collection('col').doc(data.info).valueChanges().subscribe(info => {
  this.info = info;
})

subscription is not allowed anymore.

Is there any other way of unsubscribing to all firebase subscriptions without pushing all subscriptions manually to an array and looping it before logging the user out?

Upvotes: 1

Views: 394

Answers (2)

cuddlemeister
cuddlemeister

Reputation: 1785

First of all, using nested subscriptions is not recommended in rxjs, use switchMap operator for such logic.

To manage subscriptions in the component, you can create a Subscription() and use add function on it. See the example below:

import { Subscription } from 'rxjs';

@Component({...})
export class TestComponent implements OnInit, OnDestroy {

private readonly subscriptions = new Subscription()

ngOnInit() {
    const firstSubscription = stream1$.subscribe(...);
    const secondSubscription = stream2$.subscribe(...);
    this.subscriptions.add(firstSubscription);
    this.subscriptions.add(secondSubscription);
}

ngOnDestroy() {
    this.subscriptions.unsubscribe();
}

Also here's more complex example with managing of the streams:

import { Subscription, of } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';

@Component({...})
export class TestComponent implements OnInit, OnDestroy {

ngOnInit() {
    const userSubscription = this.fb.user$.pipe(
        switchMap(data => {
            if (data) {
                return this.fb.afs.collection('col').doc(data.info).valueChanges();
            } else if (1 !== 2) { // for example
                return this.anotherService.getDataById(data.id).pipe(
                    tap((cat) => {
                        // here you can also do some intermediate logic
                        this.cats = [cat];
                    })
                )
            }
            // other things to be returned

            // don't forget to olways pass something. 
            // if nothing matched, return default value
            return of(null);
        })
    ).subscribe(info => {
        this.info = info;
    });
    this.subscriptions.add(userSubscription);
    const anotherSubscription = this.anotherService.mockMethod.subscribe(() => /** 
    something */);
    this.subscriptions.add(anotherSubscription);
}

ngOnDestroy() {
    this.subscriptions.unsubscribe();
}

}


So here you should note 2 things:

  1. what's related to your question: geta reference to the subscription userSubscription (which is returned by .subscribe() method and add it to the compoenents subscriptions. Then in ngOnDestroy unsubscribe from all the subscriptions in the compoennt. You can add as many as you want.

  2. don't use nested subscriptions. Using pipes will allow you to control your streams and make a lot of cool features, that are provided by rxjs. This is delaying, filtering, mapping and a lot of other. I recommend tou to learn more abouth flattening strategies (flatMap, switchMap, concatMap and exhaustMap). Check this article https://medium.com/@shairez/a-super-ninja-trick-to-learn-rxjss-switchmap-mergemap-concatmap-and-exhaustmap-forever-88e178a75f1b and watch a video there. It's the best explanation of this topic in my opinion.

Upvotes: 1

shuk
shuk

Reputation: 1823

perhaps pipe a takeUntil operator to each of them?

destroyed = new Subject();
destroyed$ = this.destroyed.asObservable();

constructor() {
  resource1$.pipe(takeUntil(this.destroyed$)).subscribe(...);
  resource2$.pipe(takeUntil(this.destroyed$)).subscribe(...);
  resource3$.pipe(takeUntil(this.destroyed$)).subscribe(...);
}


ngOnDestroy() {
  this.destroyed.next();
  this.destroyed.complete();
}

takeUntil: Emit values until provided observable emits.

Upvotes: 3

Related Questions