Grizzly
Grizzly

Reputation: 199

How do I prevent my observable subscription from running again for each emitted value?

I've got a lengthy bit of code with some nested subscriptions and transactions and such.

  updateActivity(value, index) {
    return this.currentDay.subscribe(docRef => {
      let dayRef = this.db.collection('days').doc(docRef[0].payload.doc.id);
      let innerRef = dayRef.collection('session').doc(index.toString())
      return this.db.firestore.runTransaction(function(transaction) {
        return transaction.get(innerRef.ref).then(function() {
          console.log("outermost")
          innerRef.valueChanges().subscribe(toRollback => {
            console.log("toRollback: " + toRollback)
            dayRef.valueChanges().subscribe(toUpdate => {
              console.log("toUpdate: " + toUpdate)
              dayRef.update({
                caloriesBurned: toUpdate["caloriesBurned"] - toRollback["caloriesBurned"], 
                sessionTime: toUpdate["sessionTime"] - toRollback["duration"] })
            })
          })
          transaction.set(innerRef.ref, value);
        })
      })
    })
  }

The gist is that this dayRef.update() call updates a collection in Firebase. Once the collection updates, the dayRef.valueChanges() subscription will realize there's a new value to emit and run again, which will keep updating dayRef repeatedly. This leads to an infinite loop.

The ideal behavior is that I subscribe to this observable, then, using the values emitted from the observable, update the Firebase collection that the observable has derived its old values from. As the subscription is still active, the dayRef.update() call will keep executing each time I update.

I understand that this is probably a fundamental misunderstanding of observables. What's happening here, logically, makes sense to me, but I'm not sure how else to use the values without subscribing unsure how to unsubscribe properly. I've tried running .unsubscribe() at the end of each observable's block, but this just unsubscribes synchronously, before the values can be emitted.

edit: with switchMap

dayRef.valueChanges().pipe(switchMap(toUpdate => 
              innerRef.valueChanges().pipe(switchMap(toRollback => 
                dayRef.update({
                  caloriesBurned: toUpdate["caloriesBurned"] - toRollback["caloriesBurned"], 
                  sessionTime: toUpdate["sessionTime"] - toRollback["duration"] })
            ))
          ))

Upvotes: 0

Views: 409

Answers (1)

Oscar Guérin
Oscar Guérin

Reputation: 76

Writing nested subscriptions is indeed a very bad practice. Chain rxjs operators instead. In your example, I think switchMap would be well suited. Depends on what you're trying to accomplish.

See documentation : https://www.learnrxjs.io/learn-rxjs/operators/transformation/switchmap

Edit :

Furthermore, try to define most of your Observable logic before the transaction. If you can, define it outside of your updateActivity() function.

For example :

        let dayRef$ = this.currentDay.pipe(
            switchMap(docRef => this.db.collection('days').doc(docRef[0].payload.doc.id))
        );
        let innerRef$ = dayRef$.pipe(
            map(dayRef => dayRef.collection('session').doc(index.toString()))
        );

represent your observables.

Upvotes: 1

Related Questions