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 topx
. - 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.