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