69

Let's say we have a root collection named 'todos'.

Every document in this collection has:

  1. title: String
  2. subcollection named todo_items

Every document in the subcollection todo_items has

  1. title: String
  2. completed: Boolean

I know that querying in Cloud Firestore is shallow by default, which is great, but is there a way to query the todos and get results that include the subcollection todo_items automatically?

In other words, how do I make the following query include the todo_items subcollection?

db.collection('todos').onSnapshot((snapshot) => {
  snapshot.docChanges.forEach((change) => {
    // ...
  });
});
Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
mechanical-turk
  • 801
  • 1
  • 8
  • 9
  • I'm running into this same problem with "pet ownership". In my search results, I need to display each pet a user owns, but I also need to be able to search for pets on their own. I ended up duplicated the data. I'm going to have a pets array property on each user AS WELL AS a pets subcollection. I think that's the best we can do with these kinds of scenarios. – technoplato Aug 29 '19 at 17:45

7 Answers7

49

This type of query isn't supported, although it is something we may consider in the future.

Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
  • 87
    Please add support for this! This is a very important feature and the Real-time database supports this so I think Firestore should too – ProfessorManhattan Nov 06 '17 at 17:58
  • What do you suggest we do now, before you come up with a solution? – Van Du Tran Nov 25 '17 at 03:52
  • Please add support for this if it makes sense – Joshua Kemmerer Dec 13 '17 at 03:02
  • 64
    Maybe I’m crazy but I fully expected this to work out of the box as a basic feature given the reference type. Otherwise, how is that type any more useful than a string? This is precisely the kind of problem that I asumed Firestore was built to solve. Please implement this! – snort Jan 03 '18 at 04:48
  • Will deep querying be anounced on the next Google I/O? Shall I wait till May 8 or is it smarter to go on with the bulky solution of making arrays on docs instead of querying... Hours of waiting (to no purpose?) or (maybe) hours of reprogramming: Difficult decision! – Arco Apr 25 '18 at 12:43
  • 5
    Today is May 27th, 2018. Is this feature available now? Was this already on Firebase's roadmap? – Anggrayudi H May 27 '18 at 11:37
  • 3
    @danmcgrath Do you have an answer with regards to the roadmap? – Nikolai Hegelstad May 28 '18 at 13:24
  • 10
    Not sure why this isn't pointed out in the docs - a bit frustrating that developers have to find this out through StackOverflow, probably because they have reached this limitation after days of coding. This is really baseline functionality, so I'm suprised that the response from Google is "may consider it for the future", without a) workarounds or b) reasons why it's only a consideration and not on the product roadmap – kiwicopple Nov 20 '18 at 05:02
  • Can the use of Map type replace some places where a subcollection is used – 6by3 Dec 07 '19 at 05:49
  • 1
    Also not sure why this feature isn't available... however, don't forget you get billed per read and I guess a populated query would count as one request only ;-) – benjiman Dec 16 '19 at 11:22
  • 2
    Has this << still >> not been added as a feature? I came across this problem using flutter + firestore and am rather perplexed. – DMonkey Mar 19 '20 at 16:29
  • It's really funny, they came across the demand three years ago !!! I think the people left it for good, till now that we thought of a great combination with flutter. But ... – Hamed Hamedi Nov 09 '20 at 14:01
  • 1
    We still see the desire for this feature, it just hasn't got near the top of the list based on all the input into the roadmap we have. – Dan McGrath Nov 09 '20 at 23:45
  • 2021/09 and we still looking for this feature – Sajad Abdollahi Sep 26 '21 at 19:36
4

If anyone is still interested in knowing how to do deep query in firestore, here's a version of cloud function getAllTodos that I've come up with, that returns all the 'todos' which has 'todo_items' subcollection.

exports.getAllTodos = function (req, res) {
    getTodos().
        then((todos) => {
            console.log("All Todos " + todos) // All Todos with its todo_items sub collection.
            return res.json(todos);
        })
        .catch((err) => {
            console.log('Error getting documents', err);
            return res.status(500).json({ message: "Error getting the all Todos" + err });
        });
}

function getTodos(){
    var todosRef = db.collection('todos');

    return todosRef.get()
        .then((snapshot) => {
            let todos = [];
            return Promise.all(
                snapshot.docs.map(doc => {  
                        let todo = {};                
                        todo.id = doc.id;
                        todo.todo = doc.data(); // will have 'todo.title'
                        var todoItemsPromise = getTodoItemsById(todo.id);
                        return todoItemsPromise.then((todoItems) => {                    
                                todo.todo_items = todoItems;
                                todos.push(todo);         
                                return todos;                  
                            }) 
                })
            )
            .then(todos => {
                return todos.length > 0 ? todos[todos.length - 1] : [];
            })

        })
}


function getTodoItemsById(id){
    var todoItemsRef = db.collection('todos').doc(id).collection('todo_items');
    let todo_items = [];
    return todoItemsRef.get()
        .then(snapshot => {
            snapshot.forEach(item => {
                let todo_item = {};
                todo_item.id = item.id;
                todo_item.todo_item = item.data(); // will have 'todo_item.title' and 'todo_item.completed'             
                todo_items.push(todo_item);
            })
            return todo_items;
        })
}
Malick
  • 477
  • 4
  • 16
  • If I try to print result todos, my subcollection are in this format: [Object][Object]... – Jonio Jul 11 '19 at 14:56
0

You could try something like this:

db.collection('coll').doc('doc').collection('subcoll').doc('subdoc')
Skully
  • 2,882
  • 3
  • 20
  • 31
Sajidh Zahir
  • 526
  • 4
  • 13
0

I have faced the same issue but with IOS, any way if i get your question and if you use auto-ID for to-do collection document its will be easy if your store the document ID as afield with the title field in my case :

let ref = self.db.collection("collectionName").document()

let data  = ["docID": ref.documentID,"title" :"some title"]

So when you retrieve lets say an array of to-do's and when click on any item you can navigate so easy by the path

ref = db.collection("docID/\(todo_items)")

I wish i could give you the exact code but i'm not familiar with Javascript

Mohammed Riyadh
  • 883
  • 3
  • 11
  • 34
0

I used AngularFirestore (afs) and Typescript:

import { map, flatMap } from 'rxjs/operators';
import { combineLatest } from 'rxjs';

interface DocWithId {
  id: string;
}

convertSnapshots<T>(snaps) {
  return <T[]>snaps.map(snap => {
    return {
      id: snap.payload.doc.id,
      ...snap.payload.doc.data()
    };
  });
}

getDocumentsWithSubcollection<T extends DocWithId>(
    collection: string,
    subCollection: string
  ) {
    return this.afs
      .collection(collection)
      .snapshotChanges()
      .pipe(
        map(this.convertSnapshots),
        map((documents: T[]) =>
          documents.map(document => {
            return this.afs
             .collection(`${collection}/${document.id}/${subCollection}`)
              .snapshotChanges()
              .pipe(
                map(this.convertSnapshots),
                map(subdocuments =>
                  Object.assign(document, { [subCollection]: subdocuments })
                )
              );
          })
        ),
        flatMap(combined => combineLatest(combined))
      );
  }
  
user2734839
  • 207
  • 3
  • 7
0

As pointed out in other answers, you cannot request deep queries.

My recommendation: Duplicate your data as minimally as possible.

I'm running into this same problem with "pet ownership". In my search results, I need to display each pet a user owns, but I also need to be able to search for pets on their own. I ended up duplicated the data. I'm going to have a pets array property on each user AS WELL AS a pets subcollection. I think that's the best we can do with these kinds of scenarios.

technoplato
  • 3,293
  • 21
  • 33
0

According to docs, you needs to make 2 calls to the firestore.. one to fetch doc and second to fetch subcollection. The best you can do to reduce overall time is to make these two calls parallelly using promise.All or promise.allSettled instead of sequentially.

GorvGoyl
  • 42,508
  • 29
  • 229
  • 225