You have unlimited access as a PRO member
You are receiving a free preview of 3 lessons
Your free preview as expired - please upgrade to PRO
Contents
Recent Posts
- Object Oriented Programming With TypeScript
- Angular Elements Advanced Techniques
- TypeScript - the Basics
- The Real State of JavaScript 2018
- Cloud Scheduler for Firebase Functions
- Testing Firestore Security Rules With the Emulator
- How to Use Git and Github
- Infinite Virtual Scroll With the Angular CDK
- Build a Group Chat With Firestore
- Async Await Pro Tips
Infinite Scroll and Pagination With Firestore and Angular
Episode 62 written by Jeff DelaneyA few months ago, I showed you how to create a basic infinite scroll feature using Realtime Database. Today, we will create a more sophisticated and flexible pagination service with Firestore to facilitate infinite scroll.
As an added bonus, the feature will be able to scroll upwards, which is useful when scrolling back in time through a text feed - think Facebook Messenger or Slack.
Get the Firestore Pagination source code.
How it Works
In Firstore, you need to make a brand new query with a document cursor each time you want more data. AngularFire2 provides this data in the snapshotChanges()
method, making it possible to map custom objects that have the raw snapshot we need as a cursor.
I experimented with a few different patterns and found my favorite approach was to use a service to keep track of a query configuration. This allows you to repeat a consistent query, while updating the cursor after each new batch. It keeps track of all source data on the service, which may or may not be desirable for your use case. The end result is an API that looks like this:
init()
- Configure the initial query.more()
- Request another batch of data.
The service provides three Observables for use in the HTML.
data
- Array of documents from Firestore.loading
- true when loading executing a query.done
- true when the end of the database is reached.
Downward Scrolling
Downward scrolling is the default usage.
Upward Scrolling
It also supports upward scrolling by prepending new items.
Scrollable Directive
ng g directive scrollable |
Direct access to the DOM will cause errors if compiling Angular for platform-server or web-worker. You can get around this by wrapping DOM access code in a try-catch
block and provide a fallback for users, such as a clickable button to load more items.
We need a directive that can tell us if we have reached the top and/or bottom of the page. Using @Output
and EventEmitter
we can create a custom event that will tell us whether the user has scrolled to the bottom of the container.
The top
and bottom
values for an element can be calculated using scroll data from the DOM.
import { Directive, HostListener, EventEmitter, Output, ElementRef } from '@angular/core'; |
When using this directive, your container div must handle overflow with a scroll bar for example:
.container { |
Using the Directive in a Component
Now let’s see how the directive works in the app.component
. First, set up an event handler in the typescript.
scrollHandler(e) { |
Then fire the handler on the custom scrollPosition
event in the HTML.
<div scrollable (scrollPosition)="scrollHandler($event)"> |
You should see the events logged in the browser console.
Loading Spinner (Optional)
ng g component loading-spinner |
I am using a loading spinner in this demo from spinkit. You can simply copy and paste the HTML and CSS from spinkit into the component files.
Pagination Service for Firestore
ng g service pagination |
My goal is to provide you with a generic service that can provide a decent level flexibility when paginating with Firestore.
Query Configuration
The QueryConfig
interface defines the options are passed to the init
method in the service. These options reproduce consistent queries and simplify components that use the service. The only required fields are path
(the collection path in firestore) and field
(the field you want the collection ordered by). All other fields are optional and will be set to defaults in the service.
interface QueryConfig { |
Service
There’s quite a bit going on here. I will try to break it down on a per-method basis.
mapAndUpdate()
is where the actual data request occurs. It maps the snapshot to the usable data and the snapshot cursor.getCursor()
If prepending documents to the feed, we need the first index, otherwise, we need the last index, or return null if it’s empty.init()
takes the query options and makes a collection reference to Firestore. It also defines thedata
Observable, which is an array of documents that can grow with future queries using the RxJSscan
operator. If prepending, we concat new elements to the beginning of the array.more()
is used for all subsequent queries. It also finds the appropriate cursor and addsstartAfter
to offset the query.
import { Injectable } from '@angular/core'; |
Using the Service in a Component
The service code was complex, but it provides a very simple API for handling infinite scroll in the component. You only need to perform two steps.
(1) Use init(path, field, opts?)
to load the initial query. Here’s how to use it.
init(path: string, field: string, opts: { |
- path - path to collection
- field - field to order by
- limit - number of docs per query
- reverse - order desc
- prepend - add new docs to start of list
(2) After the initial query, simply run more()
to add the next batch of data. Here we run it on the scroll event, but you could also use it with a click or any other event.
import { Component, OnInit } from '@angular/core'; |
The service provides three Observables we can use in the HTML.
data
- Array of data from Firestoreloading
- true when making next querydone
- true when the end of database is reached
<div class="content" scrollable (scrollPosition)="scrollHandler($event)"> |
The End
Hopefully this gives you a decent jumpstart on pagination and infinite scroll in your Angular Firestore project. This service can be customized in many ways, so please reach out if you have any questions.