I'm trying to learn rxjs and the Observable concept in general and have a scenario where I have a class of <Room>{} where <Player>{} can join in many-to-many relationship style.
In Firestore I have collection of rooms where each room has a property called players which is an array of user uids.
When a rooms component is created I subscribe to _roomService.getPlayersInRoom(roomId) which looks like below:
getPlayersInRoom(roomId: string) {
return this._db.doc<Room>(`rooms/${roomId}`).valueChanges().pipe(
map(room => room.players),
switchMap(players => players),//2
switchMap(playerId => {
if(playerId) {
return this._db.doc<Player>(`users/${playerId}`).valueChanges();
}
})
);
}
I subscribe to it later with
.subscribe(player => {
if (player) {
this.players = new Array();
this.players.push(player);
}
There are couple of issues here. My observable does not return an array of players as expected (see line //2 which transforms the string[] into a string)
Another issues is that I new up the this.players array in my component every time that room changes (otherwise the .push() will push in duplicates.
I've read documentation on some of these operators and I understand them somewhat but not enough to figure out why this code is not behaving the way it should.
First, switchMap expects you to return an Observable. If its an array-like value — it will turn it into an array using from (see this from example).
If you really want to get an array back on the stream — you should return a stream, e.g. using of: switchMap(value => of([]))
Yet, in your case, you want to substitute each id in the array with a stream. We'll need to use a combineLatest operator, for example (the name speaks for itself). Each array of players we will switch to a new stream. This new stream we'll combine of latest values on valueChanges() streams.
Heres an example:
getPlayersInRoom(roomId: string) {
return this._db.doc<Room>(`rooms/${roomId}`).valueChanges().pipe(
map(room => room.players),
switchMap(players => {
// turn array of player IDs
// into array of streams of changes
const playersStreams = players.map(
playerId => this._db.doc<Player>(`users/${playerId}`).valueChanges()
);
// combine latest changes from all streams in that array
return combineLatest(...playersStreams);
})
);
}
Then in the subscription players would be an array of combined values from valueChanges().
getPlayersInRoom(5)
.subscribe(players => {
this.players = players;
})
Please, note that there are more ways to merge values from multiple valueChanges(). Most common are: forkJoin and zip
And there are more methods to map a value on a stream
Hope this helps
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With