Pagination with Angularfire2, Inside of Angular

15 November 2016 on ionic2. 5 minutes

How do you paginate using AngularFire2?

This is a question that I have heard multiple times from people, and it sounds like a simple question. Pagination normally with standard ajax requests is very simple, you have a query that looks something like This

var query = $.ajax({
    type: 'POST',
    pageNumber: 1,
    perPage: 10
});

And then to get the next page of results you add to the page number. But when using angularfire2 with firebase, we are looking at asyncronous queries. A single call to a list would look something like this.

this.af.database.list('/items', {
    query: {
        orderByChild: 'name',
        limitToFirst: 10
    }
});

This will return a FirebaseListObservable that contains an array of 10 elements ordered by the ‘name’ property.

But were on that next level Angularfire2, and we want to make the query itself change so that we can paginate. We can do two things,

1) We can slowly increase this number to do pagination in a way that instagram does. Slowly load more items into the list as the user scrolls down.

2) Ask for different lists

2) has a fairly easy solution,

let list = this.af.database.list('/items', {
    query: {
        orderByChild: 'name',
        limitToFirst: (pagNum * 10)
    }
});

Where you just input your page number and link this list to a ngFor in your html

<div *ngFor="item in list | async">
    
</div>

1) has a more interesting solution, and its because there is a edge case we are not considering. First we want to dynamically increase the query size as we slowly scroll down. To do this we attach a BehaviorSubject to our query.

const limit:BehaviorSubject<number> = new BehaviorSubject<number>(10); // import 'rxjs/BehaviorSubject';

scrolled(): void {
  limit.next( limit.getValue() + 10);
}

this.af.database.list('/items', {
    query: {
        orderByChild: 'name',
        limitToFirst: limit
    }
});

As we scroll down, the method scrolled() gets called and we increase our limit by 10. Here lies our edge case, how will we know when we have reached the end of the list to turn off scrolling. If we don’t do this we will continue to make new firebase requests continuously until we blow up your application. This edge case it turns out is notoriously hard to handle.

To solve it we must introduce another asyncronous query that searches for the last key in the firebase list. As soon as we know what that key is, then we compare the last key in our increasingly large list to it, and if it matches we turn off the scrolling feature.

const limit:BehaviorSubject<number> = new BehaviorSubject<number>(10); // import 'rxjs/BehaviorSubject';
const lastKey: string;
const queryable: boolean = true;

// asyncronously find the last item in the list
this.af.database.list('/items' , {
    query: {
        orderByChild: 'name',
        limitToLast: 1
    }
}).subscribe((data) => {
    // Found the last key
    if (data.length > 0) {
        lastKey = data.$key;
    } else {
        lastKey = '';
    }
});

const list = this.af.database.list('/items', {
    query: {
        orderByChild: 'name',
        limitToFirst: limit
    }
});

list.subscribe( (data) => {
    if (data.length > 0) {
        // If the last key in the list equals the last key in the database
        if (data[data.length - 1].$key === lastKey) {
            queryable = false;
        } else {
            queryable = true;
        }
    }
});

scrolled(): void {
    if (queryable) {
        limit.next( limit.getValue() + 10);
    }
}

And voila, with on more subscription we can protect against bumping into the end of the list and making multiple queries. This particularly helps when usings Ionic 2 ion-infinite-scroll, because it will be called multiple (5+) times if you just hover on the end of the list.

If you want to see this in action check out our app at 38Plank and go to the ‘discover’ tab.