Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firestore: How can I re-order a collection I already have a reference to?

Is there a way to keep a reference of the same collection, but change the ordering using firestore?

TLDR: This is like the functionality I'm trying to achieve: https://stackoverflow.com/a/17320018, but since my data is from firestore, I haven't quite figured out the correct way to accomplish this dynamically.


Say I have a messagesService.ts which contains a collection of messages and a reference to an Observable of messages:

messagesCollection: AngularFirestoreCollection<Message>
messages: Observable<Message[]>;

this.messagesCollection = this.db.collection('messages', ref => {
  return ref.orderBy('dateCreated', 'desc');
});

this.messages= this.messagesCollection.valueChanges();

When I pop this data into an *ngFor="let message of messages | async" it displays the newest messages on top as expected.

Where I need help:

I'd like to be able to click a button and change the ordering of the messages (or the ordering of the data I get from firestore). For example, you could click a button to sort by messages with the most likes, or highest views, or the oldest messages, alphabetical, etc. Do I need to have a separate reference for every single way I want to sort the same collection? That seems sloppy, is there a cleaner way to do this dynamically?

Initially, I tried something like this:

sortMessagesBy(field) {
    this.messagesCollection = this.db.collection('messages', ref => {
          return ref.orderBy(field, 'desc');
    });
}

However, this didn't work because it seemed to change the reference to the collection, so the messages observable wasn't updated.

So next, I tried this:

sortMessagesBy(field) {
    this.messagesCollection = this.db.collection('messages', ref => {
          return ref.orderBy(field, 'desc');
    });
    this.messages= this.messagesCollection.valueChanges();
    return this.messages;
}

but this seems to create a new this.messages object which makes ngFor re-render the entire page and looks super sloppy (even when using trackBy).

Eventually, I'd like to narrow things down further, in the same collection where I may even have a where clause to target a specific subset of messages and be able to apply the same dynamic sorting too, I.E.:

this.messagesCollection = this.db.collection('messages', ref => {
      return ref.where("type", "==", "todo").orderBy(field, 'desc');
});

and I just don't see how I could do this without figuring out how to do it dynamically.

like image 268
cs_pupil Avatar asked Mar 28 '18 03:03

cs_pupil


People also ask

How do references work in firestore?

A DocumentReference refers to a document location in a Firestore database and can be used to write, read, or listen to the location. The document at the referenced location may or may not exist. A DocumentReference can also be used to create a CollectionReference to a subcollection.

What is subcollection in Firestore?

A subcollection is a collection associated with a specific document. Note: You can query across subcollections with the same collection ID by using Collection Group Queries.

How do I edit a collection in firestore?

Edit data. Click on a collection to view its documents, then click on a document to view its fields and subcollections. Click on a field to edit its value. To add fields or subcollections to the selected document, click Add Field or Start Collection.


2 Answers

As far as I can tell the AngularFirestoreCollection class is immutable. This means you can't change it after it's been constructed.

That is in line with the Firebase SDK classes. This code

ref.where("type", "==", "todo").orderBy(field, 'desc')

Creates a Query object, which is also immutable. To change the order of the results you will have to create a new query, and a new AngularFirestoreCollection.

like image 60
Frank van Puffelen Avatar answered Nov 14 '22 22:11

Frank van Puffelen


@Frank van Puffelen's answer helped me understand this better, and I was able to come up with something like this, that seems to work for this problem:

getMessages(type=null, field=null, direction=null) {
  console.log("sorting messages by: ", type, field, direction);
  this.messagesCollection = this.db.collection('messages', ref => {
    if (type && field) {
      return ref.where('typeId', '==', type).orderBy(field, direction? direction : 'desc');
    } else if (type && !field && !direction) {
      return ref.where('typeId', '==', type)
    } else if (!type && field) {
      return ref.orderBy(field, direction? direction : 'desc');
    } else {
      return ref;
    }
  });
  this.messages = this.messagesCollection.valueChanges();
  return this.messages;
}

This prevents me from having to keep a different variable in my service for each query I'd want which seemed like it'd be a mess. Now, in any given component, I can make a call like this:

this.messages = this.messageService.getMessages(this.type, 'views');

or this:

this.messages = this.messageService.getMessages(null, 'dateCreated');

Of Note: It did require me to create composite indexes on the typeId field along with each distinct field I plan to order by (views, dateCreated, likes, etc), but firebase makes that pretty easy.

Now, I just have to figure out why the whole page flickers when I call this function and it replaces the value in this.messages which is used in an *ngFor. It appears like it's re-rendering the entire page when I apply a different ordering even when I use trackBy.

Update:

When I subscribe to the messages in the component (instead of using the async pipe in the ngFor) it works as expected and does not re-render the entire page. Example MessageComponent.ts:

this.messageService.getMessages(this.type).subscribe(res => this.messages = res);  
like image 40
cs_pupil Avatar answered Nov 14 '22 22:11

cs_pupil