0
votes

I'm using firebase auth with Firestore (https://github.com/angular/angularfire2) in my app. With my current Firestore rules, I make sure to unsubscribe from every observable retrieved from Firestore before I signout. However, I get a "FirebaseError: Missing or insufficient permissions." error, which I traced down to a nested subscription I subscribe to within a subscription as in the below code.

Inside ngOnDestory I unsubscribe from both subscriptions, but I still get the previous error.

this.proposalSubscription = this._posts.retrieveProposals(user.uid)
  .subscribe(proposals => {
      this.proposals = proposals;
      this.proposals.forEach(proposal => {
        this.chatSubscription = this._chat.retrieveChat(proposal.id).subscribe(chat => {
          if (chat) {
            this.chats.set(proposal.id, chat)
          }
        });
      })
    });

I am almost sure now the issue is in this line: this.chatSubscription = this._chat.retrieveChat(proposal.id).subscribe(chat => ... Even though I unsubscribe from both proposalSubscription and chatSubscription before signing out, I still get the error. Any idea on how can I fix this? Also, I don't have much experience with rxjs and the operators. Is there an operator I could use to avoid this nested subscription?

Thanks in advance.

2
Hi. Why don't you nest your second observable to the first observable using concatMap from rxjs instead of subscribing inside the first subscription? It will be cleaner and you will only need to unsubscribe 1 timeJ Rui Pinto

2 Answers

2
votes

You can set up your subscription like this:

this.proposalSubscription = this._posts.retrieveProposals(user.uid)
                                    .pipe(
                                      switchMap(proposals => {

                                        //if you need to save it in class variable you can save it like this
                                        this.proposals = proposals;

                                        const obs$ = proposals.map(p => {
                                          return this._chat.retrieveChat(p.id)
                                                     .pipe(
                                                       map(chat => {
                                                         this.chats.set(p,id, chat);
                                                       })
                                                     )
                                        });

                                        return forkJoin(obs$);
                                      })
                                    )
                                    .subscribe();

You can compose your operator chain as you want and have only one subscription.

In ngOnDestory unsubscribe the subscription like this:

ngOnDestroy() {

 if(this.proposalSubscription) {
   this.proposalSubscription.unsubscribe()
 }
}

You can avoid explicit unsubscribe in ngOnDestroy and maintaining the Subscription instance by using take(1) if you expect to evaluate this._posts.retrieveProposals(user.uid) observable only once like this:

this._posts.retrieveProposals(user.uid)
                                    .pipe(
                                      take(1),
                                      switchMap(proposals => {

                                        //if you need to save it in class variable you can save it like this
                                        this.proposals = proposals;

                                        const obs$ = proposals.map(p => {
                                          return this._chat.retrieveChat(p.id)
                                                     .pipe(
                                                       map(chat => {
                                                         this.chats.set(p,id, chat);
                                                       })
                                                     )
                                        });

                                        return forkJoin(obs$);
                                      }),                                          
                                    )
                                    .subscribe();
1
votes

Nested subscriptions are not the best way to deal with dependant observables, we usually use switchMap instead. The best way to manage multiple subscriptions in the same component is using takeUntil as you can emit a value into a single subject and cancel all subscriptions in one go. We can map the propasals to an array of observables and use combineLatest to return an array of the results of those observables.

finalise = new Subject();

this._posts.retrieveProposals(user.uid)
  .pipe(
    switchMap(proposals => combineLatest(proposals.map(proposal => this._chat.retrieveChat(proposal.id)))),
    takeUntil(finalise)
  ).subscribe(chats => {
    const chat = proposals.find(p => p);
    this.chats.set(proposal.id, chat)
  });

and in ngOnDestroy

this.finalise.next();

Will end all subscriptions watching finalise in takeUntil.