如何根据文档中的参考ID合并并观察Firestore中的两个集合?

时间:2019-05-07 12:34:21

标签: javascript rxjs google-cloud-firestore observable rxfire

我正在使用Google Firestore后端创建一个StencilJS应用(无框架),并且我想尽可能地使用RxFireRxJS库来简化数据访问码。如何将来自两个使用引用ID的不同集合的数据合并到一个可观察的流中?

我已经阅读并尝试了多个在线示例,每个示例都使用了具有不同嵌套复杂度级别的运算符的不同组合。 https://www.learnrxjs.io/似乎是很好的资源,但它没有提供对我有意义的业务示例。 This question非常相似,也许唯一的不同是使用RxFire进行了一些翻译?还在看着那个。出于比较目的,在SQL中,这是一个SELECT语句,在引用ID上带有INNER JOIN

具体地说,我有一个Games的收藏集:

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

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 },
...

我想观察Game Sessions的合并集合,其中gameId实际上被Game.name取代。

我目前有一个game-sessions-service.ts,该函数具有为特定用户获取会话的功能:

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);
}

我尝试使用pipemergeMap进行各种变体,但是我不知道如何使它们正确地组合在一起。我想建立一个接口GameSessionView来代表合并后的数据:

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?
        }
    )
}

可能,我只是停留在规范化的思维方式中,因此我愿意就更好的数据管理方式提出建议。我只是不想过多的重复来保持同步。

1 个答案:

答案 0 :(得分:0)

您可以使用以下代码(也可以作为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));

说明:
使用combineLatest,您可以合并多个Obervables的最新值。如果您有"multiple (..) observables that rely on eachother for some calculation or determination",则可以使用它。
因此,假设您的GameGameSession列表是可观察的,则可以合并每个列表的值。
switchMap中,通过遍历GameSessionView来创建类型为GameSession的新对象,并使用属性iduserIdduration以及在gameName的{​​{1}}的第二个列表中找到Game的值。请注意,此示例中没有错误处理。
由于gameId期望您返回另一个Observable,因此合并列表将以switchMap返回。
最后,您可以of(gameSessionViews)进行此过程并查看预期的结果。

可以肯定的是,这不是唯一的方法,但我发现它是最简单的方法。