Reputation: 1885
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
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:
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.
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
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