vuefire icon indicating copy to clipboard operation
vuefire copied to clipboard

Firestore Add Sub-Collection Example

Open bryanpearson opened this issue 7 years ago • 13 comments

Well, I've been at it a week or too, and I'm just too green to figure out how to reference a sub-collection from within a template (if that's even possible), or what the best way to get this data would be.

What Im attempting to accomplish is loop through a collection of tickets, in which each ticket has a sub-collection of comments. I'd want to display all comments, for each ticket in the list.

Heirarchy goes: DB > Tickets Collection > ticketdocs > Comments Collection > commentdocs

app.js:

const List = {
  template: require('./list.html'),
  // template: '<div>Ticket List!</div>',
  data: function () {
    return {
      tickets: [],
    }
  },
  firestore() {
    return {
      tickets:     db.collection('tickets'),
    }
  },
}

list.html.js:

<div v-for="ticket in tickets">
  <p>{{ ticket.comments }}
</div>

Displaying/iterating over {{ ticket }} works great and binds up fine, but accessing the sub-collection is where I'm stumped.

I'm sorry for such a basic request with poor descriptions/examples, but with the new Firestore sub-collections, Im sure some folks would love some examples of how to get access to sub-collections in the various ways available.

Thank you!

bryanpearson avatar Jan 18 '18 20:01 bryanpearson

I don't think is such a basic request, it's definitely something worth looking at not easy to add in a declarative way. Unfortunately, after some research, I couldn't find a way of reading the collections from a document (in the web sdk) (https://cloud.google.com/firestore/docs/query-data/get-data). Maybe I can add some API to allow the user bind collections on the document:

this.$bind('collection', collectionRef, { onDocumentBind })
function onDocumentBind(doc, bind) {
	// bind is bound to doc
	bind('comments', db.collection(`collection/${doc.id}/comments`), { onDocumentBind: () => {} })
}

this would allow me to automatically unbind the collection when document is unbound. But I still need to check if it's feasible 🙂

posva avatar Jan 18 '18 21:01 posva

Just saw this, maybe you havent see it yet and it'll help. Thanks 👍

https://cloud.google.com/nodejs/docs/reference/firestore/0.8.x/DocumentReference#getCollections

bryanpearson avatar Jan 21 '18 13:01 bryanpearson

Unfortunately, that only exists on the node.js library 😞

posva avatar Jan 21 '18 18:01 posva

@posva I think that even the example you gave using the constructed string path (collection/${doc.id}/comments) would be a useful example for binding to a sub-collection. I ran into this issue with Vuexfire and your solution worked nicely

stevenmanton avatar Jun 24 '19 19:06 stevenmanton

I'm glad it helped! I will add it to the docs once the whole thing is available, that will be easier to understand and to contextualise in docs

posva avatar Jun 24 '19 21:06 posva

Howdy! I've just recently found this framework, and I couldn't find the aforementioned example for subcollection fetching in the docs. I'm building a music player application with the following structure: users (col) > user (autogenerated, doc) > songs (col) > song (autogenerated, doc) I'm trying to fetch the songs published by an artist, but I'm not quite sure how to pull them, even statically, let alone bound and dynamically. Since the user names are auto-generated, it becomes difficult to find and bind their sub-collections correctly. Is this something that's been addressed, and is there a better way to access this data from within the firestore(){} block? Thanks!

Edit: If this isn't quite implemented yet, I guess I can move the songs to their own top-level collection and instead add an array of reference objects to the user documents .

itsjop avatar Nov 06 '19 19:11 itsjop

@posva is there any update on this? or should I just have two bound firestore ref, 1 for the main collection (e.g. collection('articles').where(...)) and another for the subcollections (collection('articles').where(...).collection('history'))

SumNeuron avatar Feb 28 '20 14:02 SumNeuron

@SumNeuron yes, you still need to do that

posva avatar Feb 28 '20 14:02 posva

can you please provide a clean example? I am struggling

SumNeuron avatar Feb 28 '20 15:02 SumNeuron

Firestore doc's encourage users (in some cases) to nest their data in sub-collections. I think that it is likely a common practice so any documentation here on how to manage that would be helpful. Thank you for the great tool!

cookmscott avatar Oct 11 '20 13:10 cookmscott

Are there any updates on this? Any updates to this? Any updates to this?

@mfissehaye, @wonbyte, @cloudwheels - What additional update are you hoping for? Let's try to give Posva something specific to consider! Here's my understanding:

Today

Sub-collections are separate collections. Every Firestore query is limited to a single collection1. The nested relationship doesn't change this. If you want a doc AND its children, that's 2 queries. Today (AFAIK) VueFire has no special case for this.

Ex1a: Single doc + children - just bind twice (you can do this now):

bind('ticket', db.collection(TICKETS).doc(doc.id))
bind('comments', db.collection(TICKETS).doc(doc.id).collection(COMMENTS))

Ex2a: Many docs + "children" - I think your best bet is to avoid sub-collections:

bind('tickets', db.collection(TICKETS))
bind('comments', db.collection(COMMENTS)) // put ticketId into your comment docs
...
// group comments by ticket: { ticketId1: [comments], ticketId2: [comments], ... }
get keyedComments() { return groupBy(this.comments, 'ticketId') } // getter + lodash

Potential Feature Ideas (NOT AVAILABLE TODAY)

Ex1b: Single doc + children (as one object/binding) - access sub-collection as nested property:

bind('ticket', db.collection(TICKETS).doc(doc.id), { with: COMMENTS })
// ticket.comments would contain the subcollection data

IMO, not worth it, because you still need to save the comment data separately, and I don't want to accidentally push my subcollection data into my doc.

Ex2b: Many docs + children (as subcollections) - optimize binding to get children of all "tickets" in one query:

bind('tickets', db.collection(TICKETS))
bindChildren('commentsByTicket', db.collectionGroup(COMMENTS)) // collectionGroup query spans all tickets
// commentsByTicket would be keyed: { ticketId1: [comments], ticketId2: [comments], ... }

I believe this is impossible today because Firestore Collection group queries don't include any information about which parent they're nested under, so it's impossible for VueFire to group them. So we'd still need to use a ticketId property (already possible as Ex2a).

Suggested Actions

To focus the discussion, I suggest:

  1. Add the 2 valid examples (1a+2a) to the docs, then close this issue
  2. Consider parent-child relationships when assessing https://github.com/vuejs/vuefire/issues/685

1 1 collection or collection group, but the latter probably doesn't help in the parent+child case.

charles-allen avatar Jan 04 '21 01:01 charles-allen

@charles-allen How does your above example work for related comments when a user replies to a comment? Do you nest replied comments under the parent comment? How deep do you nest for additional responses to that replied comment? HOpe this makes sense...

BTW, over at fireship.io they have an example video tutorial on doing nested comments in firestore and they used AngularFire. Any chance someone can translate that to Vuefire? :)

dosstx avatar Jun 18 '21 20:06 dosstx

@charles-allen How does your above example work for related comments when a user replies to a comment? Do you nest replied comments under the parent comment? How deep do you nest for additional responses to that replied comment? HOpe this makes sense...

@dosstx - I think this is a database design problem, not a VueFire one. AFAIK, VueFire mirrors the standard Firebase SDK. If you need multiple queries through the SDK, you need multiple binds with VueFire. If your db design is optimized for the standard SDK, it's equally optimized for VueFire (which binds to the exact same query references).

Your database design needs to be driven by your business, UX, and security requirements. How to design your database is probably a better question for https://stackoverflow.com or maybe https://dba.stackexchange.com. I'd probably start by looking at 2 angles (disclaimer: I'm not a NoSQL expert!!):

  • put all comments/replies in one flat collection & re-organize the data as needed client-side; since they're strongly linked to a ticket, a sub-collection under the ticket seems reasonable (easy; flexible; but showing the comments means multiple document reads)
  • nest the comments within the ticket itself (single doc read, difficult writes & security; might hit doc size limit)

charles-allen avatar Jun 19 '21 03:06 charles-allen