Как объединить и наблюдать две коллекции в Firestore на основе ссылочного идентификатора в документах?

Я создаю StencilJS приложение (без фреймворка) с бэкэндом Google Firestore, и я хочу использовать RxFire а также RxJS библиотеки максимально упростить код доступа к данным. Как я могу объединить в один наблюдаемый поток данных, поступающих из двух разных коллекций, которые используют ссылочный идентификатор?

В Интернете есть несколько примеров, которые я прочитал и попробовал, каждый из которых использует различную комбинацию операторов с разным уровнем вложенности. https://www.learnrxjs.io/ кажется хорошим ресурсом, но он не дает примеров для бизнеса, которые имеют смысл для меня. Этот вопрос очень похож, и, возможно, единственное отличие - это какой-то перевод в использование RxFire? Все еще смотрю на это. Просто для сравнения, в SQL это было бы SELECT заявление с 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);
}

И я пробовал варианты вещей с pipe а также mergeMap, но я не понимаю, как заставить их всех правильно совмещаться. Я хотел бы установить интерфейс 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 ответ

Вы можете использовать следующий код (также доступен как 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. Его можно использовать, если у вас есть "несколько (..) наблюдаемых, которые полагаются друг на друга для какого-либо вычисления или определения".
Итак, если вы списки Gameс и GameSessions Observables, вы можете объединить значения каждого списка.
В пределах switchMap вы создаете новые объекты типа GameSessionView перебирая свой GameSessions, используйте атрибуты id, userId а также duration и найти значение для gameName во втором списке Gameс gameId, Имейте в виду, что в этом примере нет обработки ошибок.
Как switchMap ожидает, что вы вернете другой Observable, объединенный список будет возвращен с of(gameSessionViews),
Наконец, вы можете subscribe к этому процессу и увидеть ожидаемый результат.

Конечно, это не единственный способ сделать это, но я считаю это самым простым.

Другие вопросы по тегам