Tips for optimizing data loading in Angular by pre-loading data to prevent unnecessary Firebase reads

Issue: One specific part of my web application is responsible for fetching a large amount of data from Firebase Firestore whenever a user visits that section. This results in hundreds of thousands of unnecessary reads to my Firestore database.

Inquiry: How can I preload or "cache" the Firestore data so that it is readily available within the web app regardless of when any user accesses the site?

Additional Details:

  • The problematic component retrieves all documents from a Firestore collection consisting of approximately 850 documents. The content within this collection remains relatively static and primarily comprises a list of names linked to a sub-collection of cities, with each city sub-collection containing addresses where the person has lived. This component utilizes the names for a dynamic search bar that refreshes each time the user enters the component.
  • This component also fetches and displays the top x individuals with the most addresses, leading to an increased number of reads as the same function used to retrieve all people is employed to read and sort them before displaying the top x.
  • I am using Angular 13, and the website is hosted on Firebase. My familiarity with web hosting, in general, is limited, which impacts what actions I can take.

Past Attempts: I have contemplated having the web app fetch the collection upon initial loading and then populating the data accordingly once the user opens the component. While this approach reduces some reads by avoiding reading the collection every time the user clicks on the component, it still consumes significant resources as the data is fetched upon each site refresh or revisit.

Objective: Ultimately, my aim is to identify a solution that minimizes the number of reads on my Firestore database. I am open to exploring alternative approaches that address the issue, even if it involves reevaluating my current method or utilizing different resources.

Code Snippets:

firebase.service.ts:

export class FirebaseService {
  private peoplePath = '/people';
  constructor(private firestore: AngularFirestore) { }

  getAllPeople(): Observable<Person[]> {
    let colRef: AngularFirestoreCollection<any>;
    colRef = this.firestore.collection(this.peoplePath);
    return colRef.snapshotChanges().pipe(
      map(actions => actions.map(a => {
        const data = a.payload.doc.data() as any;
        const id = a.payload.doc.id;
        return { id, ...data };
      }))
    );
  }

  getPerson(personId: string) {
    let itemDoc: AngularFirestoreDocument<Person>;
    let path = this.peoplePath + "/" + personId;
    itemDoc = this.firestore.doc<Person>(path);
    return itemDoc.valueChanges();
  }

  getTopPeople(amount: number): Person[] {
    let topPeople: Person[] = [];
    this.getAllPeople().subscribe(aPeople => {
      let people = aPeople as Person[];
      people.sort((a,b) => (a.total_addresses < b.total_addresses) ? 1 : -1);
      let count = 0;
      for (let p of people) {
        topPeople.push(p);
        count++;
        if (count == amount) {
          break;
        }
      }
      return topPeople;
    });
    return topPeople;
  }

person-search.component.ts:

export class PersonSearchComponent implements OnInit {
  term: string;
  showSearch: boolean = false;
  numOfPeople: number = 20;
  numOfPeopleDisplay: number = 20;
  constructor(private fbService: FirebaseService) { }

  allPeople: Observable<Person[]> = this.fbService.getAllPeople();

  topPeople: Person[] = this.fbService.getTopPeople(20);

  ngOnInit(): void { }

  findPeople() {
    this.topPeople = this.fbService.getTopPeople(this.numOfPeople);
    this.numOfPeopleDisplay = this.numOfPeople;
  }
}

person-search.component.html:

<div>
    <input name="search"  type="text" (focus)="showSearch = true" placeholder="Search for people"  [(ngModel)]="term">
    <div class="personSearchResults" *ngIf="showSearch">
        <table>
            <tbody>
                <tr *ngFor="let person of allPeople | async | personfilter:term">
                    <td><a routerLink="/person-page/{{person.first_name}}_{{person.last_name}}">{{person.first_name}}</a></td>
                    <td><a routerLink="/person-page/{{person.first_name}}_{{person.last_name}}">{{person.last_name}}</a></td>
                </tr>
            </tbody>
        </table>
    </div>
</div>
<div>
    <span>Enter any number: <input type="number" [(ngModel)]="numOfPeople"></span>
    <button (click)="findPeople()">Sort</button>
    <p>Top {{numOfPeopleDisplay}} people by number of addresses:</p>
</div>
<div>
    <table>
        <thead>
            <tr>
                <th>Rank</th>
                <th>Name</th>
                <th>Number of addresses</th>
            </tr>
        </thead>
        <tbody>
            <tr *ngFor="let person of topPeople; let i=index;">
                <td>{{i + 1}}</td>
                <td><a routerLink="/person-page/{{person.first_name}}_{{person.last_name}}">{{person.first_name}} {{person.last_name}}</a></td>
                <td>{{person.total_addresses}}</td>
            </tr>
        </tbody>
    </table>
</div>

If further details are required, please feel free to reach out.

Answer №1

If you're searching for information on Firestore's unique feature called data bundles, then take a look at this explanation provided in the article about serving bundled Firestore content from a CDN:

With Cloud Firestore bundles, you have the ability to compile data bundles containing common query results on the backend using the Firebase Admin SDK and deliver these pre-computed blobs cached on a Content Delivery Network (CDN). This enhances user experience by speeding up the initial page load and decreases your Cloud Firestore query expenses.

To make use of this feature:

  1. Construct a bundle containing the desired documents for caching purposes.
  2. Save this bundle in a location that offers more cost-effective read operations compared to Firestore's API.
  3. Retrieve the bundle and store it in the client's local cache.
  4. Now, utilize the standard API to access the locally cached documents efficiently.

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

Substitute the existing path with an alternative route

To move to a different route in Angular 2.0, we typically use route.navigate( [ 'routeName' ] ). However, this approach adds the current route to the browser's history. Is there a technique available to substitute the current route in the ...

Relocating the node_modules folder results in syntax errors arising

I encountered a perplexing syntax error issue. I noticed that having a node_modules directory in the same location I run npm run tsc resolves the issue with no syntax errors. However, after relocating the node_modules directory to my home directory, ~ , a ...

The server encountered an issue with starting the ANCM Out-Of-Process, resulting in HTTP Error 502

We currently have two projects in progress. One involves a Web API built on .NET Core 2.2.6 and an Angular 8 Single Page Application integrated within .NET Core 2.2.6. Both projects have been deployed on IIS 7 with the Web API functioning properly, but the ...

Leveraging Runtime Environment Variables in Angular 5 within the app.module.ts File

I am currently utilizing a third-party authentication module called OktaAuthModule. In order to import it into my root module (app.module.ts), I must first configure it as follows: const config = { url: https://myurl.com/ } @NgModule({ declaration ...

Implementing nested popup windows using Bootstrap

I am facing an issue with my two-page sign-in and sign-up setup in the header of my angular2 project. On the sign-up page, I have a link that should redirect to the sign-in page when clicked, but I am struggling to make it work. Can someone provide guidanc ...

Split the massive array object into more manageable chunks

I have a large array that can range from 1 to 600K records. To work with this big array, I need to break it into smaller chunks to carry out operations. How can I achieve this effectively? Here is my proposed solution: The challenge I am facing is not kn ...

Encountering a problem with npm install due to git not being found

I recently updated my package JSON file with new dependencies and attempted to install npm, but encountered errors indicating that git could not be found: npm ERR! path git npm ERR! code ENOENT npm ERR! errno ENOENT npm ERR! syscall spawn git npm ...

I need to find a replacement for punycode in npm as it has been marked as outdated. What should

In my Angular project, I have been utilizing the npm module punycode. However, I recently noticed that VsCode is indicating that this module is deprecated. Upon further investigation at https://nodejs.org/api/punycode.html#punycode_punycode, it has been co ...

Tips on transferring a child Interface to a parent class

Here is my code snippet: LocationController.ts import {GenericController} from './_genericController'; interface Response { id : number, code: string, name: string, type: string, long: number, lat: number } const fields ...

Storing various checkbox values into a database

I have a form with checkboxes that I need to submit to an API for database storage. This is the city.page.html <ion-item *ngFor="let city of cities;"> <ion-checkbox [(ngModel)]="city.id" ></ion-checkbox> <i ...

Tips for implementing accurate structure on CloudFrontWebDistribution class

I seem to be facing an issue while trying to create a new instance of the CloudFrontWebDistribution using aws-cdk v1.7. The compiler is showing some dissatisfaction with the construct I provided. import { Stack, StackProps, Construct, App } from '@aw ...

When using the map function, I am receiving an empty item instead of the intended item based on a condition

Need assistance with my Reducer in ngRx. I am trying to create a single item from an item matching an if condition, but only getting an empty item. Can someone please help me out? This is the code for the Reducer: on(rawSignalsActions.changeRangeSchema, ...

Creating a customized Angular Material 2 component that utilizes the NG Value Accessor feature

Trying to create a custom component in Angular 4.4 + material beta12, but having trouble identifying the issue in my implementation. Here is the custom input I am attempting to achieve: Goal: Set a value to formControl once data is received from the se ...

Angular 5 does not allow function calls within decorators

I encountered an issue while building a Progressive Web App (PWA) from my Angular application. When running ng build --prod, I received the following error: ERROR in app\app.module.ts(108,64): Error during template compile of 'AppModule' Fu ...

Transfer an Array of Objects containing images to a POST API using Angular

Looking to send an array of objects (including images) to a POST API using Angular and Express on the backend. Here's the array of objects I have: [{uid: "", image: File, description: "store", price: "800"} {uid: "f ...

What is the best way to prevent jest.mock from being hoisted and only use it in a single jest unit test?

My goal is to create a mock import that will be used only in one specific jest unit test, but I am encountering some challenges. Below is the mock that I want to be restricted to just one test: jest.mock("@components/components-chat-dialog", () ...

Angular and Spring setup not showing data despite enabling Cross Origin Support

I'm currently in the process of developing a full stack application using Spring and Angular. After successfully setting up my REST APIs which are functioning properly on port 8080, I encountered an issue when trying to access them from my frontend ( ...

The argument for the e2e test is invalid because it must be a string value

Currently, I am conducting an end-to-end test for an Angular application using Protractor. However, I encountered an error while running the test for a specific feature file. The UI value that I am checking is - WORKSCOPES (2239) Below is the code snippe ...

Can a string array be transformed into a union type of string literals?

Can we transform this code snippet into something like the following? const array = ['a', 'b', 'c']; // this will change dynamically, may sometimes be ['a', 'e', 'f'] const readonlyArray = arr ...

Filtering based on the boolean value of a checkbox in Angular

I'm struggling to implement a boolean filter for my search results, separating users with financial debt from those without. I need some guidance on how to achieve this. Data Filter @Pipe({ name: 'filter' }) export class FilterPipe implem ...