I am attempting to implement an infinite scroll into my ionic angular app. Being new at this I am needing some expert advice. The problem is the more() function works the first time the scroll event happens but goes into an infinite loop when trying to get the second set of documents. Here is the code, I have included pices of the component and html and the complete pagination service.
Component.ts:
ngOnInit() {
this.page.init('Documents', 'Name', { reverse: false, prepend: false })
scrollHandler(e) {
if (e === 'bottom') {
this.page.more()
}
}
Template html:
<ion-infinite-scroll threshold="100px" (ionInfinite)="this.page.more($event)">
<ion-infinite-scroll-content
loadingSpinner="bubbles"
loadingText="Loading more data...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
pagination-service.ts:
import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/firestore';
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { tap, scan, take} from 'rxjs/operators';
// Options to reproduce firestore queries consistently
interface QueryConfig {
path: string, // path to collection
field: string, // field to orderBy
limit?: number, // limit per query
reverse?: boolean, // reverse order?
prepend?: boolean // prepend to source?
}
@Injectable()
export class PaginationService {
// Source data
private _done = new BehaviorSubject(false);
private _loading = new BehaviorSubject(false);
private _data = new BehaviorSubject([]);
private query: QueryConfig;
// Observable data
data: Observable<any>;
done: Observable<boolean> = this._done.asObservable();
loading: Observable<boolean> = this._loading.asObservable();
constructor( private afs: AngularFirestore ) { }
// Initial query sets options and defines the Observable
init(path, field, opts?) {
this.query = {
path,
field,
limit: 10,
reverse: false,
prepend: false,
...opts
}
const first = this.afs.collection(this.query.path, ref => {
return ref
.orderBy(this.query.field, this.query.reverse ? 'desc' : 'asc')
.where('Active', '==', 'Y')
.limit(this.query.limit)
})
this.mapAndUpdate(first)
// Create the observable array for consumption in components
this.data = this._data.asObservable()
.pipe(scan( (acc, val) => {
return this.query.prepend ? val.concat(acc) : acc.concat(val)
}))
}
// more() Retrieves additional data from firestore
// This works the first time but not the second time
more() {
const cursor = this.getCursor()
const more = this.afs.collection(this.query.path, ref => {
return ref
.orderBy(this.query.field, this.query.reverse ? 'desc' : 'asc')
.limit(this.query.limit)
.startAfter(cursor)
})
this.mapAndUpdate(more)
}
// Determines the doc snapshot to paginate query
private getCursor() {
const current = this._data.value
if (current.length) {
return this.query.prepend ? current[0].doc : current[current.length - 1].doc
}
return null
}
// Maps the snapshot to usable format the updates source
private mapAndUpdate(col: AngularFirestoreCollection<any>) {
if (this._done.value || this._loading.value) { return };
// loading
this._loading.next(true)
// Map snapshot with doc ref (needed for cursor)
return col.snapshotChanges()
.pipe(tap(arr => {
let values = arr.map(snap => {
const data = snap.payload.doc.data()
const doc = snap.payload.doc
return { ...data, doc }
})
// If prepending, reverse array
values = this.query.prepend ? values.reverse() : values
// update source with new values, done loading
this._data.next(values)
this._loading.next(false)
// no more values, mark done
if (!values.length) {
this._done.next(true)
}
}))
.pipe(take(1))
.subscribe()
}
// Reset the page
reset() {
this._data.next([])
this._done.next(false)
}
}
The expression assigned to the ionInfinite event is called when the user reaches that defined distance. When this expression has finished any and all tasks, it should call the complete() method on the infinite scroll instance.
In your case, you have not handled the event, just passed...
this.page.more($event)
change in Template html as below:
<ion-infinite-scroll threshold="100px" (ionInfinite)="findNext($event)">
Add method in Component.ts:
async findNext($event) {
setTimeout(async () => {
await this.page.more();
$event.target.complete();
}, 500);
}