Full disclosure: I have yet to incorporate pagination into my current application, but I have a plan for how I would tackle the task.
In a previous response, I detailed how to maintain references to Firestore doc
objects within each element of a state array bound by VuexFire or VueFire. In the first solution outlined below, we leverage these doc objects to implement Firestore's recommended cursor-based pagination for query result sets using the startAfter(doc)
query condition, as opposed to the less efficient offset
clause.
It's important to note that by utilizing Vuexfire/Vuefire, we are indicating a desire to receive live updates to our query, ensuring that our bound array reflects the desired data set accurately.
Solution #1: Pagination involves loading and displaying a horizontal slice of the entire dataset (maintaining a constant array size equal to the page size). While this doesn't align precisely with your initial request, it may be a preferable approach considering the drawbacks of other methods.
- Pros: From the server standpoint, this pagination query will execute with minimal cost and delay for large datasets.
- Pros: On the client side, it maintains a small memory footprint and renders quickly.
- Cons: Pagination navigation may not resemble traditional scrolling behaviors, likely relying on buttons for forward and backward movement.
- Page Forward: Retrieve the
doc
object from the last element of our state array and apply a startAfter(doc)
condition to the refreshed query for advancing to the next page.
- Page Backward: Requires a bit more effort. Obtain the
doc
object from the first element of the bound state array. Execute the page query with startAfter(doc)
, limit(1)
, offset(pagesize-1)
, and a reverse sort order. This yields the initial doc (pageDoc) of the preceding page. Then utilize startAfter(pageDoc)
, a forward sort order, and limit(pageSize)
to rebind the state array (similar query to Page Forward but with doc = pageDoc).
Please note that in a broader context, it's debatable whether retaining pageDoc values from earlier pages (to avoid the reverse query) is viable. As we treat this as a 'live' updated filtered list, the number of remaining items from previous pages may have drastically changed since scrolling down. Depending on your specific application's expectations of rate of change, retaining past pageDoc values may prove advantageous.
Solution #2: Advancing pages enlarges the query result and bound array.
Pros: Offers a user experience akin to traditional scrolling as the array expands.
Pros: Eliminates the need for using a serializer
workaround, as startAfter()
or endBefore()
are unnecessary.
Cons: Server-side, you're reloading the entire array up to the new page from Firestore each time you rebind for a new page and receiving real-time updates for a growing array. The multitude of doc reads could become costly.
Cons: Client-side rendering may slow down with page progression - although shadow DOM may provide a remedy. UI flickering during reloads may necessitate additional UI tricks to ensure seamless transition (e.g., delayed rendering until the array is fully updated).
Pros: Potentially well-suited for applications utilizing an infinite scrolling feature, though some testing may be warranted.
Page Forward: Increment the pageSize
within the query limit and rebind, triggering a Firestore re-query and full reload.
Page Backward: Decrement the pageSize
from the query limit and rebind/reload accordingly (or not!). May require adjusting the scroll position as well.
Solution #3: A hybrid of Solution #1 and #2. This approach involves utilizing live Vuexfire/Vuefire binding for a slice of the query/collection (as in Solution #1) and employing a computed function to concatenate it with an array already containing loaded data pages.
- Pros: Reduces Firestore query costs and delays, maintaining a smooth scrolling experience conducive to Infinite scrolling UI implementation. Where's the Kool-Aid?
- Cons: Requires meticulous tracking of the displayed array segment to ensure it remains bound and receives live updates.
- Page Forward/Backward: Same process as Solution #1 for binding the current data page, except now there's a need to copy the previous page of data into the non-live array and devise a small computed function to
concat()
the two arrays for UI list binding.
Solution #3a: A slight workaround involves substituting invisible earlier data pages with a div
of equivalent height ;) to maintain the appearance of scrolling continuity. As you scroll back, the concealed prior page div
must be replaced with newly bound data. For seamless infinite scrolling UX, preloading an additional page ahead or behind is recommended to ensure timely loading well before reaching the page transition point. Not all infinite scroll APIs support this feature.
Solutions #1 & #3 may benefit from a Cookbook PR to VueFire or a promising MIT-licensed / NPM library. Any volunteers?