如何在Angular(6)和Firebase Firestore设置中分页回到上一页

时间:2018-08-13 22:03:57

标签: angular firebase firebase-realtime-database pagination google-cloud-firestore

在将Angular与Cloud Firestore(Firebase)结合使用时,似乎有很多示例,教程和视频,以及如何分页到下一页。

但是经过大量搜索,我找不到找到前一页的方法。 我得到的最远的就是返回第一页。

这是我的服务现在的外观:

import { Injectable } from '@angular/core';
import { AngularFirestore } from 'angularfire2/firestore';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Task } from './task.model';

@Injectable()
export class TaskService {
  private _data: BehaviorSubject<Task[]>;
  public data: Observable<Task[]>;
  firstEntry: any;
  latestEntry: any;

  constructor(private afs: AngularFirestore) { }

  public first() {
    this._data = new BehaviorSubject([]);
    this.data = this._data.asObservable();

    const tasksRef = this.getCollection('tasks', ref => ref.orderBy('createdAt').limit(5))
      .subscribe(data => {
        this.firstEntry = data[0].doc;
        this.latestEntry = data[data.length - 1].doc;
        this._data.next(data);
      });
  }

  public next() {
    const tasksRef = this.getCollection('tasks', ref => ref.orderBy('createdAt').startAfter(this.latestEntry).limit(5))
      .subscribe(data => {
        if (data.length) {
          this.firstEntry = data[0].doc;
          this.latestEntry = data[data.length - 1].doc;
          this._data.next(data);
        }
      });
  }

  public previous() {
    const tasksRef = this.getCollection('tasks', ref => ref.orderBy('createdAt').endBefore(this.firstEntry).limit(5))
      .subscribe(data => {
        if (data.length) {
          this.firstEntry = data[0].doc;
          this.latestEntry = data[data.length - 1].doc;
          this._data.next(data);
        }
      });
  }

  private getCollection(ref, queryFn?): Observable<any[]> {
    return this.afs.collection(ref, queryFn).snapshotChanges().pipe(
      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 };
        });
      })
    );
  }
}

初始加载(第一页)有效,并且所有后续页面均按预期工作。 但是看来endBefore(this.firstEntry)的光标不正确,并再次出现在第一页。

导航回上一页的正确方法是什么?

如果有人知道完整的教程或可行的分页器示例,请分享...

3 个答案:

答案 0 :(得分:0)

import * as firebase from 'firebase/app';
import { last } from 'lodash';
import { dropRight } from 'lodash';

安装此npm软件包(Lodash,Firebase)

在导入Firebase之后,从lodash持续并放置dropRight。 Last方法返回数组的最后一个元素。 DropRight方法可切片数组的最后一个元素。

allEntry: Array<any[]> =  [];
firstEntry = [];
lastEntry;

创建三个变量,并将firstEntry分配给数组,以便在查询中存储第一个数据。 AllEntry存储从查询中检索到的所有数据。 LastEntry将最后一个元素存储在allEntry中,该元素将用作游标来获取之后的数据。

getMainEntry() {
return this.afs.collection('tasks', ref => ref.orderBy('createdAt').limit(5)).valueChanges().subscribe(data => {
  this.allentry = data;
  firebase.firestore().collection('tasks').doc(data[data.length - 1]['key']).onSnapshot(c => {
    this.lastentry = c;
    firebase.firestore().collection('tasks').doc(data[0]['key']).onSnapshot(da => {
      this.firstEntry.push(da);
    });
  });
});
}

此getMainEntry函数获取集合中的数据,按createdAt对其进行排序,并将其限制为5。然后获取allEntry数组中最后一个元素的documentsnapshot并将其分配给lastEntry。之后,将allEntry数组中第一个元素的documentsnapshot推送到firstEntry数组中。

现在让我们创建下一个函数

getNextData(){
  const entarr = [];
  firebase.firestore().collection('tasks').orderBy('createdAt').startAfter(this.lastEntry).limit(5).get().then((data) => {
    data.docs.map(a => {
      entarr.push(a.data());
      this.allEntry = entarr;
    });
  }).then(() => {
    firebase.firestore().collection('tasks').doc(entarr[entarr.length - 1]['key']).onSnapshot(data => {
      this.lastEntry = data;
    });
  }).then(() => {
    firebase.firestore().collection('tasks').doc(entarr[0]['key']).onSnapshot(da => {
      this.firstEntry.push(da);
    });
  });
}

getNextData函数获取集合中的下一个数据,按createdAt对其进行排序,并将其限制为lastEntry之后的下5个数据。然后获取并将entarr数组中最后一个元素的documentsnapshot分配给lastEntry。之后,将allEntry数组中第一个元素的documentsnapshot推送到firstEntry数组中。

上一个功能

getPrevData() {
const entarr = [];
  this.firstEntry = dropRight(this.firstEntry);
const firstEntry = last(this.firstEntry);
firebase.firestore().collection('tasks').orderBy('createdAt').startAt(firstEntry).limit(5).get().then((d) => {
  d.docs.map(a => {
    entarr.push(a.data());
    this.allEntry = entarr;
    });
  }).then(() => {
    firebase.firestore().collection('tasks').doc(entarr[entarr.length - 1]['key']).onSnapshot(da => {
      this.lastEntry = da;
    });
  });
}

希望这会很有用。

答案 1 :(得分:0)

精简代码

paginate(navigation) { 

    switch (navigation) {
      case 'first':
        this.dataQuery = this.itemDoc.collection('photos')
          .ref.orderBy('modifiedDate', 'desc').limit(2);
        break;
      case 'next':
        this.dataQuery = this.itemDoc.collection('photos')
          .ref.orderBy('modifiedDate', 'desc').startAfter(this.lastVisible).limit(2);
        break;
      case 'prev':
        this.firstVisible = dropRight(this.firstVisible);
        const firstVisible = last(this.firstVisible);
        this.dataQuery = this.itemDoc.collection('photos')
          .ref.orderBy('modifiedDate', 'desc').startAt(firstVisible).limit(2);
        break;
    }
      
    this.dataSource = this.dataQuery.get().then((documentSnapshots:QuerySnapshot<any>)=>{

      if(navigation != 'prev') {
        this.firstVisible.push(documentSnapshots.docs[0]);
      }
      this.lastVisible = documentSnapshots.docs[documentSnapshots.docs.length -1];
            
      return documentSnapshots.docs.map((element:QueryDocumentSnapshot<any>)=>{
        const data = element.data();
        const id = element.id;
        return { id, ...data };
      })
    });
  }
  paginatorOnChange(paginatorObj) {
    //console.log(paginatorObj);
    this.pageIndex = paginatorObj.pageIndex;
    this.paginate(paginatorObj.previousPageIndex<paginatorObj.pageIndex? 'next': 'prev');
  }

答案 2 :(得分:0)

在进行了一次较小的更新后,您的服务似乎可以正常工作,但是我仍然有以下问题,我想听听您或与之相关的任何人的想法...

首先,进行更新:

最近发布的Firebase SDK版本7.3.0提供了一种新的查询方法:char *ptr = 0; std::cout << &ptr; // address where the pointer is placed std::cout << (void*)ptr; // address managed by the pointer = 0 ++ptr; std::cout << &ptr; // this value never changes std::cout << (void*)ptr; // Now this value should be 1

而且,我已经通过在您的limitToLast(n:number)方法中使用该查询方法对您的服务进行了正常测试。

我更改了此内容

previous()

对此:

const tasksRef = this.getCollection('tasks', ref => ref.orderBy('createdAt').endBefore(this.firstEntry)
.limit(5))
.subscribe(...)

但是,正如我上面所说,我还有一些问题:

是不是每次对const tasksRef = this.getCollection('tasks', ref => ref.orderBy('createdAt').endBefore(this.firstEntry) .limitToLast(5)) // <-- using the new query method here .subscribe(...) first()next()的调用都会为每个请求实例化新的订阅?如果是这样,您不应该在后续请求之前取消订阅吗?

我看到您在每个方法中都将每个预订存储到一个previous()常量中,而且我不清楚原因为何,除非有意取消订阅尚未写入您的代码中的其他代码示例代码?

尽管如此,我正在尝试以与您相同的方式实现分页,同时仍保持用于实时更新的侦听器。在我的特定情况下,我知道在我需要分页的集合中,所有文档都不会更改,顺序也不会改变,而只是可以将新文档添加到集合中,并且id喜欢实时监听其他文档。取消订阅似乎会使实现该目标的方法变得复杂。

任何人都有洞悉我们是否应该担心在对已发布的示例实现进行分页之前取消订阅的情况吗?