0
votes

I am not so into RxJS and I have the following doubt about why it is the best way to solve this problem. I will try to explain it in details. Into a service class I have these 2 methods:

  acceptArtistBid(bid): void {

    this.findArtistBidsAppliedByCurrentWall(bid)
    .subscribe(artistsBisdList => {
      console.log("ARTISTS BIDS LIST RELATED THE CURRENT WALL: ", artistsBisdList);

      // ITERATE ON EACH ELEMENT OF THE artistsBisdList, DO SOME CHANGE TO EACH ELEMENT AND UPDATE ALL THESE ELEEMENTS ON FIRESTORE
      
    }); 
  }

  findArtistBidsAppliedByCurrentWall(bid):Observable<Bid[]> {
    return this.db.collection('bids',
    ref=> ref.where("wallId", "==", bid.wallId))
    .snapshotChanges()
    .pipe(
        map(snaps => {

            const courses = this.convertSnaps<Bid>(snaps);

            return courses;
        })
    )
  }

So basically I have this acceptArtistBid() method that call the findArtistBidsAppliedByCurrentWall() in order to retrieve an Observable containing a Bid objects array.

At the moment inside my acceptArtistBid() method I am subscribing this observable in order to retrieve the list of objects...then I have to iterate on this list in order to apply some logic to each of these object (I simply have to change the value of a status field) and then update these object on Firestore database.

Using this approach at the moment I was thinking to do a simple classical for loop interation or maybe the forEach() method passing an iterator function as parameter (as shown here: https://www.w3schools.com/js/js_array_iteration.asp) in order to change the status field value of each object in my array and then update these object on Firebase one by one.

I am asking if this approach is correct or if it is possible do something better using RxJS....in this case I think that I have not to subscribe my findArtistBidsAppliedByCurrentWall(bid) method but that maybe I have to use some RxJS operators directly on the Observable.

Is it possible? What could be an elegant solution using RxJS to solve this problem?

2

2 Answers

1
votes

My answer is: use a simple forEach. You could do the same by piping the Observable<Bid[]> through a map and convert your objects there, but I think you're not going to convert those, but pass them to a method or similar. And for that: why would you use an observable-structure?

On the other hand if you're going to manipulate that observable, filtering, removing, adding, mapping etc. you would use the the pipe.

1
votes

If I understand your problem, I think that the right answer depends on the requirements of this use case.

First of all you have to consider that reading from Firestore and writing to Firestore are 2 asynchronous streams. In particular for writing, the stream could emit at each element update or it could be a batch update that emits only once, when the entire batch of elements is updated.

Said that, you need to understand whether these 2 streams are independent from each other or not. More specifically, if you can return the result of the read and, in parallel, update Firestore or if you need to be sure Firestore is updated before returning the result of the update to the client.

Then you need to understand how you want to manage errors. Is an error on update going to be treated differently than an error on read? In the rest of the answer I am not going to cover this part, but it can not be ignored.

Let's assume that you are in a scenario where you MUST update all elements in Firestore before returning the result to the client. In this case I would use and approach along these lines.

First create a method, in the service, which returns an Observable which emits the list of bids read from Firestore only after each element has been updated.

acceptArtistBid(bid) {
  // define a variable to hold the list of bids
  let listOfBids: Array<Bid>;
  // notice that the Observable is returned by this method and not subscribed in the service
  return this.findArtistBidsAppliedByCurrentWall(bid).pipe(
    // here you use the map of Observable to transform the list, and within this map you call the map Array method to run the transformation on each bid
    map(artistsBisdList => {
       listOfBids = artistsBisdList.map(bid => // transform bid);
       return listOfBids
    }),
    // assume updateFirestore$ is a method that returns an Observable representing the update operation - this second map operator returns an array of Observables
    map(updatedList => updatedList.map(updatedBid => updateFirestore$(bid))),
    // forkJoin is an RxJs function that accepts an array of Observables and emits when all Observables have completed - in this case it emits when all elements have been updated on Firestore
    concatMap(listOfObservables => forkJoin(listOfObservables)),
    // this last map is just to make the method return listOfBids
    map(() => listOfBids)
  )
}

Then I would let the client of the service decide when and where to subscribe to the Observable returned by acceptArtistBid method.

The above code example has not been tested and therefore may contain some errors, but I hope it transmits the sense of the logic. Consider also that it would be possible to remove the listOfBids variable but this probably would make the code less readable, at least according to my taste.

Generally speaking it is a good rule to think that service methods return Observables rather then subscribe to them. It should be the responsibility of the client to subscribe to them. This is the reactive approach to building applications, where first you define the streams you are interested in, often wiring simple streams to create more complex ones†, and then you subscribe to them.

With this approach you end up with few, often just one, streams representing the entire logic of the application and therefore few, if not just one, subscription.