Search code examples
angularfirebasegoogle-cloud-firestorerxjsangularfire

Firebase - Return multiple queries as a collectionData?


The "in" operator works when the number of equality is 10 or less. When it is larger than 10, I will partition the array into multiple sub-arrays of length 10 and initialize multiple query requests.

When the length is less than 10, I am simply using q as the query request and returning return collectionData(q, { idField: 'id' });. However, when I have q0, q1, etc., I don't know how to return them all the same way as if returning q.

The question is how to return q0, q1, ... using the same syntax as just returning q?

constructor(private firestore: Firestore) {
}

getUserChats() {
    const userId = '12345678910';
    const userRef = doc(this.firestore, `users/${userId}`);
    return docData(userRef).pipe(
        switchMap((data) => {
            let userChats = data.chats;
            let chatsRef = collection(this.firestore, 'chats');
            if (userChats.length <= 10) {
                //use the query normally as the 'in' operator has a limit of 10
                let q = query(chatsRef, where(documentId(), 'in', userChats));
                return collectionData(q, { idField: 'id' });
            } else {
                //breakdown the userChats array into sub-arrays of length 10
                //it is hardcoded for proof-of-concept
                let q0 = query(chatsRef, where(documentId(), 'in', userChats.slice(0, 10)));
                let q1 = query(chatsRef, where(documentId(), 'in', userChats.slice(10, 20)));
                //...
                //how to combine q0 and q1?
                return collectionData(????????????????, { idField: 'id' });
            }
        })
    );
}

Solution

  • Here is the solution I have come up with:

    • Divide the data into segments of size 10 (the last segment might have 10 or less)
    • For each query, use the in operator for each segment, which contains at most 10 equalities
    • Each query will be stored in an array of queries
    • collectionData is then used for each query in the array, which returns an observable
    • All the observables will be stored inside an array of observable
    • The function combineLatest from RxJS is then used with the array of observables and it then returned
      getUserChats() {
        const userId = 'aabbccdd';
        const userRef = doc(this.firestore, `users/${userId}`);
        return docData(userRef).pipe(
          switchMap((data) => {
            if (data && data.chats) {
              const userChats = data.chats;
              const chatsRef = collection(this.firestore, 'chats');
              let queries = [];
              if (userChats.length !== 0) {
                for (let i = 0; i < userChats.length; i += 10) {
                  let q: Query<DocumentData> = query(
                    chatsRef,
                    where(documentId(), 'in', userChats.slice(i, i + 10))
                  );
                  queries.push(q);
                }
    
                let observablesData: Observable<DocumentData[]>[] = [];
                for (let i = 0; i < queries.length; i++) {
                  let x = collectionData(queries[i], { idField: 'id' });
                  observablesData.push(x);
                }
                return combineLatest(observablesData);
              }
            }
            return [null];
          })
        );
      }
    

    To test the function, ensure to check for the null case, as this will thrown an exception:

    this.chatService
          .getUserChats()
          .pipe()
          .subscribe((result: FirebaseChatroom[]) => {
            if (result !== null) {
              result = result.flat();
              //do something with result...
            }
          });