Estou trabalhando com Observables aqui e o wrapper AngularFire, mas foi assim que consegui fazer isso.
É meio louco, ainda estou aprendendo sobre observáveis e possivelmente exagerei. Mas foi um bom exercício.
Alguma explicação (não é um especialista RxJS):
- songId $ é um observável que emitirá ids
- dance $ é um observável que lê aquele id e obtém apenas o primeiro valor.
- ele então consulta o collectionGroup de todas as canções para encontrar todas as instâncias dele.
- Com base nas instâncias que atravessa para o pai Danças e obtém seus ids.
- Agora que temos todos os ids de dança, precisamos consultá-los para obter seus dados. Mas eu queria que ele tivesse um bom desempenho, então, em vez de consultar um por um, agrupei-os em grupos de 10 (o angular máximo necessário para uma
in
consulta.
- Acabamos com N depósitos e precisamos fazer N consultas no firestore para obter seus valores.
- depois de fazermos as consultas no firestore, ainda precisamos realmente analisar os dados desse.
- e, finalmente, podemos mesclar todos os resultados da consulta para obter um único array com todas as Danças nele.
type Song = {id: string, name: string};
type Dance = {id: string, name: string, songs: Song[]};
const songId$: Observable<Song> = new Observable();
const dance$ = songId$.pipe(
take(1), // Only take 1 song name
switchMap( v =>
// Query across collectionGroup to get all instances.
this.db.collectionGroup('songs', ref =>
ref.where('id', '==', v.id)).get()
),
switchMap( v => {
// map the Song to the parent Dance, return the Dance ids
const obs: string[] = [];
v.docs.forEach(docRef => {
// We invoke parent twice to go from doc->collection->doc
obs.push(docRef.ref.parent.parent.id);
});
// Because we return an array here this one emit becomes N
return obs;
}),
// Firebase IN support up to 10 values so we partition the data to query the Dances
bufferCount(10),
mergeMap( v => { // query every partition in parallel
return this.db.collection('dances', ref => {
return ref.where( firebase.firestore.FieldPath.documentId(), 'in', v);
}).get();
}),
switchMap( v => {
// Almost there now just need to extract the data from the QuerySnapshots
const obs: Dance[] = [];
v.docs.forEach(docRef => {
obs.push({
...docRef.data(),
id: docRef.id
} as Dance);
});
return of(obs);
}),
// And finally we reduce the docs fetched into a single array.
reduce((acc, value) => acc.concat(value), []),
);
const parentDances = await dance$.toPromise();
Copiei, colei meu código e alterei os nomes das variáveis para o seu, não tenho certeza se há algum erro, mas funcionou bem para mim. Avise-me se encontrar algum erro ou se puder sugerir uma maneira melhor de testá-lo com talvez algum Firestore simulado.