Reputation:
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 uid
s.
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.
Upvotes: 2
Views: 5497
Reputation: 5364
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
Upvotes: 9