【问题标题】:AngularFire2 infinite scrollingAngularFire2无限滚动
【发布时间】:2017-06-24 10:16:57
【问题描述】:

我正在尝试使用 Ionic2 和 Firebase 实现无限滚动。

我使用 AngularFire2。我想做的是将新项目添加到获取的列表中,而不是重新加载整个列表。

let query$:Observable<any> = this.af.database.list(`quests/`, {
    query: {
        orderByChild: 'date_published',
        limitToFirst: this.recentChunkLimit$ //Subject where I push new limit length
    }
}).publishReplay(1).refCount();

但是,当我这样查询列表时,整个列表每次都会通过 websockets 重新加载,从而使每次下一次更新变得越来越慢。 这是网络 websockets 选项卡的屏幕截图: 而且我还注意到每个下一个块都会发出 2 次请求(尽管我放了 publishReplay)。它发生在我使用 AngularFire2 的所有应用程序中。 不过我可能会误解一些东西。我确实需要澄清一下。

//==========编辑=============

现在,我以某种方式设法实现了我想要的,而无需每次都重新加载整个列表。不是最好的实现,但它有效。基本上,我创建了一个可观察数组,并通过订阅下一个可观察块(我还得到最后一个开始的元素)将新值加载到其中。 但是后面的问题仍然存在 - 在套接字显示中,我收到了 2 次请求的数据。

【问题讨论】:

  • 整个列表是什么意思?您请求了 5 个项目。然后您请求了 10 个。前 5 个将是您请求的 10 个的一部分。这不是你的期望吗?
  • 我的意思是从火力基地请求整个列表,因此再次加载已经加载的项目。这是预期的行为,但绝对不是期望的行为,因为对于每个更高的限制,加载时间都会增加。

标签: angular firebase ionic2 infinite-scroll angularfire2


【解决方案1】:

query 选项使用 observables 是行不通的。底层 SDK 中没有动态修改查询的 limitToFirst 的功能,并且在 AngularFire2 中也无法做到这一点。

每次可观察的 query 选项发出一个新值时,都会创建一个新的 Firebase ref。你可以在source here看到它。

但是,可以通过执行以下操作来创建一个表示无限列表的 observable:

import { Observable } from "rxjs/Observable";
import { Subject } from "rxjs/Subject";
import rxjs/add/observable/defer";
import rxjs/add/observable/zip";
import rxjs/add/operator/concatMap";
import rxjs/add/operator/filter";
import rxjs/add/operator/first";
import rxjs/add/operator/map";
import rxjs/add/operator/scan";
import rxjs/add/operator/share";
import rxjs/add/operator/startWith";

const pageSize = 100;
let notifier = new Subject<any>();
let last: Observable<any>;

let infiniteList = Observable

  // Use zip to combine the notifier's emissions with the last
  // child value:

  .zip(notifier, Observable.defer(() => last))

  // Use concatMap to emit a page of children into the
  // composed observable (note that first is used to complete
  // the inner list):

  .concatMap(([unused, last]) => this.af.database.list("quests", {
      query: {

        // If there is a last value, start at that value but ask
        // for one more:

        limitToFirst: last ? (pageSize + 1) : pageSize,
        orderByChild: "date_published",
        startAt: last
      }
    })
    .first()
  )

  // Use scan to accumulate the page into the infinite list:

  .scan((acc, list) => {

    // If this isn't the initial page, the page was started
    // at the last value, so remove it from the beginning of
    // the list:

    if (acc.length > 0) {
      list.shift();
    }
    return acc.concat(list);
  }, [])

  // Use share so that the last observable (see below) doesn't
  // result in a second subscription:

  .share();

// Each time a page is emitted, map to its last child value so
// that it can be fed back into the composed infinite list:

last = infiniteList
  .filter((list) => list.length > 0)
  .map((list) => list[list.length - 1].date_published)
  .startWith(null);

infiniteList.subscribe((list) => console.log(list));

// Each time the notifier emits, another page will be retrieved
// and added to the infinite list:

notifier.next();
notifier.next();
notifier.next();

这可行,但如果您订购的子项具有重复值,AngularFire2 将无法可靠地翻阅结果,直到重新打开并解决 this issue

结果列表是静态的。也就是说,如果数据库发生变化,已经分页到列表中的孩子不会被更新。实现动态列表更具挑战性,因为重复和丢失的孩子很容易受到基于限制的分页机制的影响。


自从撰写此答案以来,我已经在我开源的 Firebase 可观察对象库中提供了经过测试的正向和反向、非实时和实时无限列表可观察对象的实现。见this GitHub repo

【讨论】:

  • 感谢您的回答。我以不同的方式解决了它。你的解决方案更漂亮。这是一个很好的 RxJS 课程。您能否也看看问题的第二部分。不知道是不是应该这样;
  • 第二部分是指信息似乎被发送了两次吗?明天我会快速浏览一下。
  • 关于显示两次检索数据的 WebSocket 流量,您发现了我认为是错误的地方。我正在和 AngularFire2 团队讨论这个问题。
  • 仅供参考:相关的issuePR
  • 我见过的最具描述性的答案之一!解决了我的问题,效果很好,谢谢。这实际上是一个 RxJS 课程!
【解决方案2】:

要添加到cartant's answer,如果您希望从末尾开始并反向检索列表项,请执行以下操作(我已在更改代码的地方添加了 cmets)。

import { Observable } from "rxjs/Observable";
import { Subject } from "rxjs/Subject";
import rxjs/add/observable/defer";
import rxjs/add/observable/zip";
import rxjs/add/operator/concatMap";
import rxjs/add/operator/filter";
import rxjs/add/operator/first";
import rxjs/add/operator/map";
import rxjs/add/operator/scan";
import rxjs/add/operator/share";
import rxjs/add/operator/startWith";

const pageSize = 100;
let notifier = new Subject<any>();
let last: Observable<any>;

let infiniteList = Observable

  .zip(notifier, Observable.defer(() => last))

  .concatMap(([unused, last]) => this.af.database.list("quests", {
      query: {

        // Use limitToLast to move upward the list instead of downward

        limitToLast: last ? (pageSize + 1) : pageSize,
        orderByChild: "date_published",

        // Use endAt to start at the end of the list

        endAt: last
      }
    })
    .first()
  )

  .scan((acc, list) => {

    // Swap the roles of acc and list, as we want to 
    // concatenate from the beginning

    if (list.length > 0) {
      acc.shift();
    }
    return list.concat(acc);
  }, [])

  .share();



last = infiniteList
  .filter((list) => list.length > 0)

  // Use the first child in this list as the next endAt value

  .map((list) => list[0].date_published)

  // Use undefined instead of null, as endAt: null in angularfire2
  // will search for the last child that is null

  .startWith(undefined);

infiniteList.subscribe((list) => console.log(list));

notifier.next();
notifier.next();
notifier.next();

【讨论】:

    猜你喜欢
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 2013-03-09
    • 2012-10-03
    • 2020-12-05
    • 2015-09-07
    • 2012-05-11
    • 2017-01-25
    相关资源
    最近更新 更多