Solving Rapid Clicking: Paging Controls with Asynchronous server-side pagination

Today, I want to tell you about the hiccups in moving from infinite scroll paging pattern to forward and back buttons.

How we Paged Through Infinite Scroll:

The http services called by the infinite scroll datasource take two pieces of information from the last record and treats them as a page token. Requesting a new page with those two pieces of data gives you the next page. If you ever receive a page of data that is smaller than your requested page size you know you’ve hit the end. Data is cached by the datasource.

New HTTP Service:

The newest service takes an actual “nextPageToken” that is returned with every page of data.

The New Paging Source

The simplified version of my new paging source includes:

An array to hold the page tokens for requested pages where the last token is the current viewed or actively requested page.

//A variable to hold the next page token.
let nextPageToken;
// to be called when the promise for data resolves.
// This function sets data, counts, and the new nextPageToken.
function updateDateContent() 
// Pop a token for “the current page” and 
// make a request for the new last token.
function fetchPrevious()
// Add the next token to the history and make a page request. 
function fetchNext()
// A function for the actual request of data.
function fetchPage(dataProvider) 

The biggest difference between the infinite scroll and the paging buttons: User interaction.

Fast Clicking Next:

When a user rapidly pages forward, we can’t add the current “nextPageToken” to history when we are already waiting on the same page.

If you don’t check “is the last page token === to my current nextPageToken” before making a new request- you end up pushing the nextPageToken into the history array for every click the person makes.

Limitation: Because the next page token arrives with every page, you can only move forward one page at a time. This is the same experience our users had on the infinite scroll.

Fast Clicking Previous:

Popping tokens off and sending out a request only to have no idea when it would return. You can imagine there are some potential issues here: How do we guarantee we update with the most recent request to resolve? What if the 2nd click takes longer than the 3rd click? We’d update again with out of date data.

Closures allow us to solve our problem:

A closure is the combination of a function and the lexical environment within which that function was declared.

Each paging source object is in charge of keeping a reference to the most recent promise.

Each call to the fetchPage() function has a local variable “myPromise”

When we send out our request, we set both myPromise and mostRecentPromise.

Whenever a promise resolves, we simply ask if myPromise is referencing the same object as the mostRecentPromise.

If the promise reference matches we know that the resolved promise is the response to the most recent request and we can proceed with displaying that data.


this.fetchPage = function(dataProvider, otherProviders) {
        var myPromise = this.mostRecentPromise = $q.all(dataProvider, otherProviders);
        myPromise.then( results => {
            if (currentFetch === this.mostRecentPromise) {
              this.loadingData = false;

*I used other providers to get data such as counts and the awesome $q.all lets them resolve together.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )


Connecting to %s