AngularFire2 无限滚动
AngularFire2 infinite scrolling
我正在尝试使用 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 次请求的数据。
对 query
选项使用 observables 是行不通的。底层 SDK 中没有工具可以动态修改查询的 limitToFirst
,并且在 AngularFire2 中也无法做到这一点。
每次可观察的 query
选项发出一个新值时,都会创建一个新的 Firebase 引用。您可以在 source here.
中看到它
但是,可以通过执行以下操作来创建表示无限列表的可观察对象:
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();
这会起作用,但是如果您订购的 child 有重复值,AngularFire2 将无法可靠地翻阅结果,直到 this issue 为 re-opened并解决了。
结果列表是静态的。也就是说,如果数据库更改,children 已经分页进入列表将不会更新。实施动态列表更具挑战性,因为 children 很容易受到 limit-based 分页机制的影响。
自从写下这个答案后,我已经在我开源的 Firebase 可观察对象库中提供了正向和反向、non-realtime 和实时无限列表可观察对象的经过测试的实现。参见 this GitHub repo。
为了补充 ,如果您希望从末尾开始并反向检索列表项,请按以下步骤操作(我在更改代码的地方添加了注释)。
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();
我正在尝试使用 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 次请求的数据。
对 query
选项使用 observables 是行不通的。底层 SDK 中没有工具可以动态修改查询的 limitToFirst
,并且在 AngularFire2 中也无法做到这一点。
每次可观察的 query
选项发出一个新值时,都会创建一个新的 Firebase 引用。您可以在 source here.
但是,可以通过执行以下操作来创建表示无限列表的可观察对象:
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();
这会起作用,但是如果您订购的 child 有重复值,AngularFire2 将无法可靠地翻阅结果,直到 this issue 为 re-opened并解决了。
结果列表是静态的。也就是说,如果数据库更改,children 已经分页进入列表将不会更新。实施动态列表更具挑战性,因为 children 很容易受到 limit-based 分页机制的影响。
自从写下这个答案后,我已经在我开源的 Firebase 可观察对象库中提供了正向和反向、non-realtime 和实时无限列表可观察对象的经过测试的实现。参见 this GitHub repo。
为了补充
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();