简体   繁体   中英

How to merge and observe two collections in Firestore based on reference ID in documents?

I'm creating a StencilJS app (no framework) with a Google Firestore backend, and I want to use the RxFire and RxJS libraries as much as possible to simplify data access code. How can I combine into a single observable stream data coming from two different collections that use a reference ID?

There are several examples online that I've read through and tried, each one using a different combination of operators with a different level of nested complexity. https://www.learnrxjs.io/ seems like a good resource, but it does not provide line-of-business examples that make sense to me. This question is very similar, and maybe the only difference is some translation into using RxFire? Still looking at that. Just for comparison, in SQL this would be a SELECT statement with an INNER JOIN on the reference ID.

Specifically, I have a collection for Games :

{ id: "abc000001", name: "Billiards" },
{ id: "abc000002", name: "Croquet" },
...

and a collection for Game Sessions :

{ id: "xyz000001", userId: "usr000001", gameId: "abc000001", duration: 30 },
{ id: "xyz000002", userId: "usr000001", gameId: "abc000001", duration: 45 },
{ id: "xyz000003", userId: "usr000001", gameId: "abc000002", duration: 55 },
...

And I want to observe a merged collection of Game Sessions where gameId is essentially replace with Game.name .

I current have a game-sessions-service.ts with a function to get sessions for a particular user:

import { collectionData } from 'rxfire/firestore';
import { Observable } from 'rxjs';
import { GameSession } from '../interfaces';

observeUserGameSesssions(userId: string): Observable<GameSession[]> {

    let collectionRef = this.db.collection('game-sessions');
    let query = collectionRef.where('userId', '==', userId);

    return collectionData(query, 'id);
}

And I've tried variations of things with pipe and mergeMap , but I don't understand how to make them all fit together properly. I would like to establish an interface GameSessionView to represent the merged data:

export interface GameSessionView {
    id: string,
    userId: string,
    gameName: string,
    duration: number
}

observeUserGameSessionViews(userId: string): Observable<GameSessionView> {

    this.observeUserGameSessions(userId)
    .pipe(
        mergeMap(sessions => {
            // What do I do here? Iterate over sessions 
            // and embed other observables for each document?
        }
    )
}

Possibly, I'm just stuck in a normalized way of thinking, so I'm open to suggestions on better ways to manage the data. I just don't want too much duplication to keep synchronized.

You can use the following code (also available as Stackblitz ):

const games: Game[] = [...];
const gameSessions: GameSession[] = [...];

combineLatest(
  of(games),
  of(gameSessions)
).pipe(
  switchMap(results => {
    const [gamesRes, gameSessionsRes] = results;
    const gameSessionViews: GameSessionView[] = gameSessionsRes.map(gameSession => ({
      id: gameSession.id,
      userId: gameSession.userId,
      gameName: gamesRes.find(game => game.id === gameSession.gameId).name,
      duration: gameSession.duration
    }));
    return of(gameSessionViews);
  })
).subscribe(mergedData => console.log(mergedData));

Explanation:
With combineLatest you can combine the latest values from a number of Obervables. It can be used if you have "multiple (..) observables that rely on eachother for some calculation or determination" .
So assuming you lists of Game s and GameSession s are Observables, you can combine the values of each list.
Within the switchMap you create new objects of type GameSessionView by iterating over your GameSession s, use the attributes id , userId and duration and find the value for gameName within the second list of Game s by gameId . Mind that there is no error handling in this example.
As switchMap expects that you return another Observable, the merged list will be returned with of(gameSessionViews) .
Finally, you can subscribe to this process and see the expected result.

For sure this is not the only way you can do it, but I find it the simplest one.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM