如何在 Angular(6) 和 firebase Firestore 设置中分页返回前一页

Posted

技术标签:

【中文标题】如何在 Angular(6) 和 firebase Firestore 设置中分页返回前一页【英文标题】:Howto paginate back to previous pages in a Angular(6) and firebase Firestore setup 【发布时间】:2019-01-20 16:58:12 【问题描述】:

在将 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) 没有保持正确的光标,并再次导致第一页。

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

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

【问题讨论】:

【参考方案1】:

通过一次小的更新,您的服务似乎可以正常工作,但是我还有一些问题,如下所述,很想听听您或任何对此有知识的人的想法...

首先,更新:

最近发布的 Firebase SDK 7.3.0 版本提供了一种新的查询方法: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() 都会在每个请求中实例化新订阅吗?如果是这样,您不应该在后续请求之前取消订阅吗?

我看到您在每个方法中都将每个订阅存储到 tasksRef 常量中,我不清楚为什么,除非您打算在您的示例代码中尚未编写的代码中的其他地方取消订阅?

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

任何人都知道在分页特定于发布的示例实现之前我们是否应该担心退订?

【讨论】:

【参考方案2】:

精简代码

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');
  

【讨论】:

【参考方案3】:
import * as firebase from 'firebase/app';
import  last  from 'lodash';
import  dropRight  from 'lodash';

安装这个 npm 包(Lodash、Firebase)

从 lodash 导入 firebase 后,last 和 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 数组中最后一个元素的文档快照并将其分配给 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;
    );
  );

希望这会有用。

【讨论】:

以上是关于如何在 Angular(6) 和 firebase Firestore 设置中分页返回前一页的主要内容,如果未能解决你的问题,请参考以下文章

Angular 6.0 firebase 托管部署不起作用

PayPal Rest API、firebase 函数和 Angular 6 出现 CORS 错误

在 Angular 6 中解码 firebase/php-jwt

Firebase 托管在部署 Angular 6 应用程序后显示欢迎页面?

找不到在 Angular 中导入 Firebase 身份验证

Angular 6 - 上传后获取 Firebase 存储文件的下载 URL