Search code examples
javascriptfirebasegoogle-cloud-firestorelistener

Firestore: How to listen to both collection and subcollection


In Firebase Firestore, I have a collection wherein each doc contains an id array a subcollection; like this:

collection: households
>>> doc: household1, members = [1]
>>> >>> collection entries
>>> >>> >>> entry 1, <data>
>>> >>> >>> entry 2, <data>
>>> doc: household2, members = [1, 2]
>>> >>> collection entries
>>> >>> >>> entry 3, <data>
>>> >>> >>> entry 4, <data>
>>> >>> >>> entry 5, <data>

I want to query all entries where user 1 is a member of. I want to do this with listeners so that my data updates when (1) the households change or when (2) the entries change.

How do I do this?

I've tried to query first the corresponsing households and afterwards get the corresponding entries, like this:

// Loop trough all households of user
db.collection("households")
  .where("members", "array-contains", uid)
  .where("status", "==", "active")
  .onSnapshot((snapshotChange) => {
    
    // Loop trough entry of each household
    snapshotChange.forEach((householdsDoc) => {
      db.collection("households")
        .doc(householdsDoc.id)
        .collection("entries")
        .onSnapshot((snapshotChange) => {
          snapshotChange.forEach((doc) => {
            // Prepare entry
            let currentDoc = doc.data();
            currentDoc["id"] = doc.id;
            // Handle change according to type
            snapshotChange.docChanges().forEach((change) => {
              console.log(change.type, "change.doc.id", change.doc.id);
              if (change.type === "added") {
                this.entries.push(currentDoc);
              } else if (change.type === "modified") {
                let index = this.entries.findIndex(
                  (el) => el.id === change.doc.id
                );
                if (index > -1) {
                  this.entries.splice(index, 1);
                }
                this.entries.push(currentDoc);
              } else if (change.type === "removed") {
                let index = this.entries.findIndex(
                  (el) => el.id === change.doc.id
                );
                if (index > -1) {
                  this.entries.splice(index, 1);
                }
              }
            });
          });
        });

For this code, I get this from the console:

enter image description here

As you can see, somehow some ids arrive multiple times. Why is that?


Solution

  • Reads and listeners in Firestore are shallow. There is no way to perform a read/query on households and also get entries from the subcollections in one go.

    You can either:

    1. Perform a query on the household collection and then a separate read for the subcollection of each matching household document.
    2. Duplicate the members data into each Entry document in the subcollection, and then use a collection group query to query across all Entry collections for the members you need.

    Neither of these is pertinently better than the other, so look at your specific use-case to see what results in the fewest reads/cost and best performance.