Service

One asset I like in Angular is the separation of concern. We create a new service which should take care of interacting with the database.

$ ionic g service feed

In the following lines I’ll use the interface and generic name Item to represent the data we are looking to fetch and I’ll declare these interfaces in the same class as our service. Replace it with the real description of your data 😉.

import {DocumentReference} from '@angular/fire/firestore';



interface ItemData {

title: string;

content: string;

}



interface Item {

id: string;

ref: DocumentReference;

data: ItemData;

}

In our newly created service, we declare the following variables:

itemsSubject : a state container for our items lastPageReached : another state, a boolean , to notice if we have or not yet fetched all the data nextQueryAfter : a reference to the last Firestore document fetched to index our database queries paginationSub and findSub : two subscriptions to stop observing the changes and to clean the memory when needed

Moreover, we also declare a service to interact with Firestore, a method destroy to unsubscribe the observers and we expose two functions to return our subjects as observables.

import {Injectable} from '@angular/core';



import {AngularFirestore, DocumentReference, QueryDocumentSnapshot}

from '@angular/fire/firestore';

import {BehaviorSubject, Subscription} from 'rxjs';



@Injectable({

providedIn: 'root'

})

export class FeedService {

private itemsSubject: BehaviorSubject<Item[] | undefined> =

new BehaviorSubject(undefined); private lastPageReached: BehaviorSubject<boolean> =

new BehaviorSubject(false);



private nextQueryAfter: QueryDocumentSnapshot<ItemData>;



private paginationSub: Subscription;

private findSub: Subscription;



constructor(private fireStore: AngularFirestore) {

} destroy() {

this.unsubscribe();

}



private unsubscribe() {

if (this.paginationSub) {

this.paginationSubscription.unsubscribe();

}



if (this.findSub) {

this.findSubscription.unsubscribe();

}

}



watchItems(): Observable<Item[]> {

return this.itemsSubject.asObservable();

}



watchLastPageReached(): Observable<boolean> {

return this.lastPageReached.asObservable();

}

}

We have to query the data in Firestore step by step respectively using a pagination because we are going to implement an infinite scroller. For that purpose, Google provides startAfter which instruct the database to “skip” the matching entities before the given start point. It is also worth to notice that in order to be able to perform such queries, we also need to sort these with orderBy and that I limited the pagination to 10 elements per step with the option limit .

find() {

try {

const collection: AngularFirestoreCollection<ItemData> =

this.getCollectionQuery();



this.unsubscribe();



this.paginationSub = collection.get()

.subscribe(async (first) => { this.nextQueryAfter = first.docs[first.docs.length - 1] as

QueryDocumentSnapshot<ItemData>;



await this.query(collection);

});

} catch (err) {

throw err;

}

}



private getCollectionQuery(): AngularFirestoreCollection<ItemData> {

if (this.nextQueryAfter) {

return this.fireStore.collection<ItemData>('/items/', ref =>

ref.orderBy('created_at', 'desc')

.startAfter(this.nextQueryAfter)

.limit(10));

} else {

return this.fireStore.collection<ItemData>('/items/', ref =>

ref.orderBy('created_at', 'desc')

.limit(10));

}

}

To that point, we have implemented a find function which query the database, therefore we can now develop the part where we collect the results and add these to our state container.