Firestore query subcollections

后端 未结 11 1500
余生分开走
余生分开走 2020-11-22 09:24

I thought I read that you can query subcollections with the new Firebase Firestore, but I don\'t see any examples. For example I have my Firestore setup in the following way

11条回答
  •  [愿得一人]
    2020-11-22 09:39

    I'm working with Observables here and the AngularFire wrapper but here's how I managed to do that.

    It's kind of crazy, I'm still learning about observables and I possibly overdid it. But it was a nice exercise.

    Some explanation (not an RxJS expert):

    • songId$ is an observable that will emit ids
    • dance$ is an observable that reads that id and then gets only the first value.
    • it then queries the collectionGroup of all songs to find all instances of it.
    • Based on the instances it traverses to the parent Dances and get their ids.
    • Now that we have all the Dance ids we need to query them to get their data. But I wanted it to perform well so instead of querying one by one I batch them in buckets of 10 (the maximum angular will take for an in query.
    • We end up with N buckets and need to do N queries on firestore to get their values.
    • once we do the queries on firestore we still need to actually parse the data from that.
    • and finally we can merge all the query results to get a single array with all the Dances in it.
    type Song = {id: string, name: string};
    type Dance = {id: string, name: string, songs: Song[]};
    
    const songId$: Observable = 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();
    
    

    I copy pasted my code and changed the variable names to yours, not sure if there are any errors, but it worked fine for me. Let me know if you find any errors or can suggest a better way to test it with maybe some mock firestore.

提交回复
热议问题