Last active
February 3, 2023 15:47
-
-
Save peavers/0a6d3cd6104b017c0f8476cad5c43f4b to your computer and use it in GitHub Desktop.
Enables infinite/virtual scrolling with ngx-virtual-scroller and Firestore
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Injectable} from '@angular/core'; | |
import {BehaviorSubject, Observable} from "rxjs"; | |
import {AngularFirestore} from "@angular/fire/firestore"; | |
import {map, mergeMap, scan, tap, throttleTime} from "rxjs/operators"; | |
import {ChangeEvent} from "ngx-virtual-scroller"; | |
/** | |
* Enables infinite scrolling with ngx-virtual-scroller and Firestore. Most of the hard work goes to | |
* https://angularfirebase.com/lessons/infinite-virtual-scroll-angular-cdk. | |
* | |
* @author Chris Turner ([email protected]) | |
*/ | |
@Injectable({ | |
providedIn: 'root' | |
}) | |
export class FirestoreScrollService { | |
batch = 10; | |
theEnd = false; | |
offset = new BehaviorSubject(null); | |
items: Observable<any[]>; | |
constructor(private firestore: AngularFirestore) { | |
} | |
/** | |
* Gets the inital page of data from Firestore. | |
* @param path String path of collection | |
* @param field String key to use for ordering | |
*/ | |
public getFirstPage(path: string, field: string) { | |
const batchMap = this.offset.pipe( | |
throttleTime(500), | |
mergeMap(number => this.getBatch(path, field, number)), | |
scan((acc, batch) => { | |
return {...acc, ...batch}; | |
}, {}) | |
); | |
this.items = batchMap.pipe(map(value => Object.values(value))); | |
} | |
/** | |
* Gets a collection of items from Firebase. | |
* @param path String path of collection | |
* @param field String key to use for ordering | |
* @param offset String the value of the last item | |
* | |
* @return Observable holding the mapped value | |
*/ | |
public getBatch(path: string, field: string, offset: string): Observable<{}> { | |
return this.firestore | |
.collection(path, reference => reference | |
.orderBy(field) | |
.startAfter(offset) | |
.limit(this.batch)) | |
.snapshotChanges() | |
.pipe( | |
tap(arr => (arr.length ? null : (this.theEnd = true))), | |
map(arr => { | |
return arr.reduce((acc, cur) => { | |
return {...acc, [cur.payload.doc.id]: cur.payload.doc.data()}; | |
}, {}); | |
}) | |
); | |
} | |
/** | |
* Trigger an event to fetch the next batch of items. | |
* @param event ChangeEvent from ngx-virtual-scroll | |
* @param offset String value of the last item | |
* @param totalItems Int count of objects rendered 'scroll.cachedItemsLength' seems to work | |
*/ | |
public nextBatch(event: ChangeEvent, offset: string, totalItems: number) { | |
// If we have everything just return | |
if (this.theEnd) { | |
return; | |
} | |
// If the last item we got from the event is also the end of the rendered view, fetch more | |
if (event.endIndex === totalItems - 1) { | |
this.offset.next(offset); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="section"> | |
<div class="container"> | |
<ng-container *ngIf="fss.items | async as items"> | |
<virtual-scroller #scroll class="scroll-window" | |
[parentScroll]="scroll.window" | |
[items]="items" | |
(end)="fss.nextBatch($event, (items[items.length - 1].name), scroll.cachedItemsLength)"> | |
<div class="columns is-multiline item-list" #container> | |
<item-object*ngFor="let item of scroll.viewPortItems" [item]="item"></item-object> | |
</div> | |
</virtual-scroller> | |
</ng-container> | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Component, OnInit} from '@angular/core'; | |
import {FirestoreScrollService} from "../../services/firestore-scroll.service"; | |
@Component({ | |
selector: 'some-component', | |
templateUrl: './some-component.html', | |
}) | |
export class SomeComponent implements OnInit { | |
constructor(public fss: FirestoreScrollService) {} | |
ngOnInit(): void { | |
this.fss.getFirstPage(`/items`, 'name'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment