How do I rearrange the order of a collection in Firestore using a reference that I already have?

Is there a method to maintain a reference of the same collection while altering the ordering using firestore?

TLDR: I am looking to achieve similar functionality as demonstrated in: , but since my data is sourced from firestore, I am struggling to find the right approach to accomplish this dynamically.


Imagine I have a messagesService.ts file which holds a collection of messages and an Observable reference to these 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 this data is displayed using

*ngFor="let message of messages | async"
, it showcases the latest messages at the top, just as expected.

Assistance Required:

I would like to implement a feature where users can click a button to change the order of messages (or the order of retrieved data from firestore). For instance, sorting messages by most likes, highest views, oldest messages, alphabetical order, etc. Do I need a separate reference for each sorting criteria? This approach seems clunky, so is there a more efficient way to accomplish this dynamically?

Initially, I attempted something like this:

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

However, this did not work well because it appeared to modify the reference to the collection, resulting in the messages observable not being updated.

So, I tried another approach:

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

But this creates a new this.messages object, causing ngFor to re-render the entire page, making it appear messy (even with trackBy implemented).

In the long run, I aim to delve deeper within the same collection, possibly incorporating a where clause to target specific subsets of messages while maintaining dynamic sorting capabilities, e.g.:

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

It seems challenging to achieve this without solving the dynamic sorting puzzle first.

Answer №1

It appears that the AngularFirestoreCollection class is immutable, meaning it cannot be altered once it has been created.

This aligns with the behavior of Firebase SDK classes. The following code snippet

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

Generates a Query object, which is also immutable. To modify the result ordering, you will need to create a new query and a fresh AngularFirestoreCollection.

Answer №2

After gaining insights from @Frank van Puffelen's explanation, I was able to devise a solution for the problem at hand. The following code snippet showcases my approach:

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 method eliminates the need for separate variables in my service for each query scenario, simplifying the process. Now, within any component, I can utilize calls like below:

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

or like this:

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

A Point to Note: Creating composite indexes on the typeId field and distinct fields intended for ordering (such as views, dateCreated, likes, etc) was necessary. Fortunately, creating these indexes in Firebase proved to be straightforward.

However, an issue arises when the page flickers upon calling this function, replacing the value in this.messages which is utilized in an *ngFor loop. It seems that the entire page re-renders even with trackBy implemented.

Update:

To address the rendering issue, subscribing to the messages within the component instead of using the async pipe in ngFor yielded positive results. Below is an example taken from MessageComponent.ts:

this.messageService.getMessages(this.type).subscribe(res => this.messages = res);  

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Struggling to get Axios working in Node despite having it properly installed

I am encountering an issue with my Jasmine test that involves HTTP requests. Despite having Axios installed using the command npm install axios --save, I keep getting the error message axios is not defined. var request = require('axios'); var co ...

I encountered an error while attempting to utilize a Reference Template variable or $event in Angular. Why is this happening?

[Check out this Header.component.html fileI encountered an error while passing a function parameter ...

Have you ever wondered why a listener on the 'data' event interprets two separate requests as one, unless a timeout is added?

It seems that there is a tricky issue with node where it combines 2 different requests into one unless a timeout is added. In the code snippet below, if we write 'one' and 'two' to the server and push the result to an array, node interp ...

Managing date validation for a three-part field using AngularJS

I am currently working on validating a three-part date field using AngularJS. I have managed to create a custom validation function, but I am struggling with determining how the fields should update each other's status. How can I ensure that all thre ...

What is the best way to initialize React-map-gl with the user's current location as the default latitude and longitude?

Is there a way to render the map with default values of viewport set to user's location without needing to click a locate me button? <ReactMapGL mapboxApiAccessToken={mapboxApiKey} mapStyle="mapbox://styles/mapbox/streets-v11" ...

Unleashing the Power of Dynamic JSON Data Access

I am facing an issue with my React Child component. Here is the code snippet: const SingleProject =(props)=>{ let data = projectData.VARIABLE_FROM_PROPS.projectDetails; let asideData = projectData.VARIABLE_FROM_PROPS.projectSideBar; useEffe ...

What is the best way to show input choices once an option has been chosen from the 'select class' dropdown menu?

When it comes to displaying different options based on user selection, the following HTML code is what I've been using: <select class="form-control input-lg" style="text-align:center"> <option value="type">-- Select a Type --</opti ...

When executing a JavaScript program, an error with the message 'MODULE_NOT_FOUND' appeared, causing the internal module loader in Node to throw an error at line 1145

node:internal/modules/cjs/loader:1145 throw err; ^ Error: Module 'C:\Users\sande\3D Objects\JavaScript-Lesson\lesson08.js' not found at Module._resolveFilename (node:internal/modules/cjs/loader:1142:15) at Mo ...

How can JavaScript be integrated with Django static URLs?

I have a Django application where I store static images on Digital Ocean Spaces. Displaying these static images in my template is simple:<img>{% static 'images/my_image.png' %}</img> When I inspect the HTML page after loading, I see ...

Removing an item from an array containing several objects

I have an array that looks like this: var participants = [ {username: "john", time: null}, {username: "samira", time: null}, {username: "mike", time: null}, {username: "son", time:null} ] To remove an item based on the username, I can do the f ...

Tips for modifying the function of an Electron application using an external JavaScript file

This is the project structure I envision. https://i.stack.imgur.com/ocRp9.jpg kareljs folder houses an Electron app, and upon running npm start within that directory, a window appears and executes the run method of karel.js when the button Run Karel is ...

Transforming with Babel to create pure vanilla JavaScript

Our current development process involves working with a custom PHP MVC Framework that combines HTML (Views), PHP files, and included JS with script tags (including JQuery, Bootstrap, and some old-fashioned JS libs). During the development stages, we want ...

Step-by-step guide on building an engaging Donut chart using jQuery

After spending several hours working on it, I'm struggling to draw my donut graph with JavaScript. Can anyone provide a solution? I am looking for a way to add 25% when one checkbox is selected and +25% when two checkboxes are selected. Thank you in a ...

What steps are involved in developing a jQuery Validator function to enforce username restrictions?

Currently, I have implemented a method to enforce strong passwords like this: $.validator.addMethod('goodPassword', function(value, element){ return this.optional(element) || value.length >= 6 && /\d/.test(val ...

What is the process for AngularJS to automatically populate the initial tag value in an SVG template?

I'm currently working on an AngularJS directive that needs to update different attributes within an svg tag. angular.module("complexNumbers") .directive("cartesianPlane", function() { return { restrict: "E", scope: { }, templateUrl: " ...

The function designed to create in-line TailwindCSS classNames may work inconsistently in React and NextJS environments

Currently, I am utilizing TailwindCSS to style a web application. One frustrating aspect is that when attempting to add before: and after: pseudo-elements, it must be done individually. For instance, take the navigation drop-down button styling: <span ...

Is there a way to arrange an array based on the product or quotient of two values?

I'm working with an array of posts, each containing data on 'views' and 'likes', along with the user IDs associated with those likes. My goal is to sort this array based on the like rate. However, my current approach seems to be i ...

A step-by-step guide on how to use ajax/jquery to access an external webpage without relying on iframe

Is there a more effective way to call another page, such as http://www.google.com, and load it into my specific div without using an iframe? I attempted to do so with ajax... $.ajax({ url : 'http://www.google.com', success : function ( ...

"Passing Data from Angular Parent Component to Child Component

At first, the parent component and child don't have any connection. It's just a list of elements. However, when the user clicks on the list, the child component is loaded. Then, I can call a method of the child component from the parent using @Vi ...

Adjusting the width of row items in Angular by modifying the CSS styles

I am envisioning a horizontal bar with items that are all the same width and evenly spaced apart. They can expand vertically as needed. Check out the updated version here on StackBlitz Issue: I am struggling to automatically set the width of the row elem ...