Search code examples
angularrxjsgoogle-cloud-firestoreangularfire2

Firestore + AngularFire2 pagination ( query items by range - .startAfter(lastVisible) )


In a component I want to pull a range of items from FireStore, for ex. from 0 to 5, from 5 to 10 etc. I found this in FireStore's docs, but they dont use AngularFire2 so as harder I tried to refactor as bigger mess I got. I made it working by simply splice()'ing:

service ->

topFirstScores(): AngularFirestoreCollection<Score> {
  return this.fireRef.collection('scores', r => r
          .orderBy('score', 'desc').limit(6)
  );
}

component ->

$scores = new Subject();

this.scores$ = this.$scores.asObservable();
if (this.scores === 'first') {
  this.scoreS.topFirstScores().valueChanges().take(1)
    .subscribe(_ => this.$scores.next(_.splice(0, 3)))
} else {
  this.scoreS.topFirstScores().valueChanges().take(1)
    .subscribe(_ => this.$scores.next(_.splice(3, 3)))
}

But this seems more as a workaround. Could anyone translate this:

var first = db.collection("cities")
        .orderBy("population")
        .limit(25);

return first.get().then(function (documentSnapshots) {
  // Get the last visible document
  var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
  console.log("last", lastVisible);

  // Construct a new query starting at this document,
  // get the next 25 cities.
  var next = db.collection("cities")
          .orderBy("population")
          .startAfter(lastVisible)
          .limit(25);
});

That preferably returns AngularFirestoreCollection<T>?


Solution

  • I had the same Problem and this is what i did.

    Service

    private _data: BehaviorSubject<Scores[]>;
    public data: Observable<Scores[]>;
    latestEntry: any;
    
    constructor(private afs: AngularFirestore) {}
    
    // You need to return the doc to get the current cursor.
      getCollection(ref, queryFn?): Observable<any[]> {
        return this.afs.collection(ref, queryFn).snapshotChanges().map(actions => {
          return actions.map(a => {
            const data = a.payload.doc.data();
            const id = a.payload.doc.id;
            const doc = a.payload.doc;
            return { id, ...data, doc };
          });
        });
      }
    // In your first query you subscribe to the collection and save the latest entry
     first() {
      this._data = new BehaviorSubject([]);
      this.data = this._data.asObservable();
    
      const scoresRef = this.getCollection('scores', ref => ref
        .orderBy('score', 'desc')
        .limit(6))
        .subscribe(data => {
          this.latestEntry = data[data.length - 1].doc;
          this._data.next(data);
        });
      }
    
      next() {
        const scoresRef = this.getCollection('scores', ref => ref
          .orderBy('scores', 'desc')
           // Now you can use the latestEntry to query with startAfter
          .startAfter(this.latestEntry)
          .limit(6))
          .subscribe(data => {
            if (data.length) {
              // And save it again for more queries
              this.latestEntry = data[data.length - 1].doc;
              this._data.next(data);
            }
          });
      }
    

    Component

      scores$: Observable<Scores[]>;
      ...
      ngOnInit() {
        this.yourService.first();
        this.scores$ = this.yourService.data;
      }
    
      nextPage() {
       this.yourService.next();
      }