Thursday, 3 August 2017

Firebase cursor implementaion


Fetching data in firebase is very easy and fast but fetching large amount of data at once is a bad idea. In this case, we have to use pagination. Unfortunately, Firebase doesn't have built in pagination but we can achieve by using its predefined methods like startAt(), endAt(), limitToFirst() and limitToLast() methods.In this blog, let's achieve pagination using those predefined methods.

For better understanding let's implement pagination for the following data.

"users": {
    1: "",
    2: "",
    3: "",
    4: "",
    5: "",
    6: "", // cursor
    7: "",
    8: "",
    9: "",
    10: "",
    11: "",
    12: "",
    13: "",
    14: "",
    15: "",
    16: "",
    17: "",
    18: "",
    19: "",
    20: ""
}

The idea is if we want to fetch 5 records per page then we should fetch (5 + 1) records, the sixth record acts as a cursor for fetching next set of records. If fetched records count is less than per page record count (here it is 5) then there are no records to fetch further and no cursor.

The code looks like this :
firebase.database().ref("/users")
              .orderByKey().startAt(<cursor>)
              .limitToFirst(<perPage> + 1)
              .once("value").then((data) => {
                       // handle data and save last record id as cursor.
              })


As we know firebase stores and retrieves data in ascending order, the above code fetches the data in that order because we are using startAt() and limitToFirst() methods. If we want in descending order we need to use endAt() and limitToLast() methods.

Then the code looks like this :


firebase.database().ref("/users")
              .orderByKey().endAt(<cursor>)
              .limitToLast( + 1)
              .once("value").then((data) =&gt; {
                       // handle data and save cursor.
              });

I had written a class which handles all the things which we discussed above.
pagination.ts
//Typescript version
class Pagination {
    public dataRef: string;
    public perPage: number;
    public order: string;
    public hasRecords: boolean = true;

    private _pageRecordCount: number = 0;
    private _cursor: string = "";

    constructor(dataRef: string, perPage?: number, order?: string){
        this.dataRef = dataRef;
        this.perPage = perPage || 10;
        this.order = order || 'dsc';
    }

    private _handleData(data: any){
        let currentRecords = [];
        this.hasRecords = false;
        this._pageRecordCount = 0;
        this._cursor = "";
        if(data){
            let entities = data;
            let entityKeys = Object.keys(entities);

            if(entityKeys.length > this.perPage){
                (this.order == 'asc') ? (this._cursor = entityKeys.pop()) : (this._cursor = entityKeys.shift());
                this.hasRecords = true;
            }
            
            for(let entity of entityKeys){
                this._pageRecordCount++;
                if(this._pageRecordCount <= this.perPage){
                    let tempData = entities[entity];
                    tempData["id"] = entity;
                    (this.order == 'asc') ? (currentRecords.push(tempData)) : (currentRecords.unshift(tempData));
                }
            }
        }
        return new Promise((resolve, reject) => {
            resolve(currentRecords);
        });
    }

    public fetchRecords(){
        let databaseRef = firebase.database().ref(this.dataRef);
        let orderByRef = databaseRef.orderByKey();
        let filterRef;
        let cursorRef;

        if(this._cursor){
            cursorRef = (this.order == 'asc') ? orderByRef.startAt(this._cursor) : orderByRef.endAt(this._cursor);
        }else{
            cursorRef = orderByRef;
        }

        if(this.order == 'asc'){
            filterRef = cursorRef.limitToFirst(this.perPage + 1);
        }else {
            filterRef = cursorRef.limitToLast(this.perPage + 1);
        }

        return filterRef.once("value").then((data) => {
            return this._handleData(data.val());
        });
        
    }
}


Now you can use the above pagination class in the following manner.

// initialize object
// new Pagination(<ref>, <perPage>, <order type 'asc' or 'dsc'>)
let pagination = new Pagination("/users", 5, 'asc'); 
//for fetching records
pagination.fetchRecords().then((data) =&gt; {
 //data
})

//hasRecords indicates that there are probably more results
pagination.hasRecords 

Demo URL:
https://jayaram-blog-examples.firebaseapp.com/pagination/

You can find the whole code in the following URLs:
Typescript:
https://gist.github.com/kurapatijayaram/32f4dc8e49948b07a9caba1a6b4449c7

Javascript:
https://gist.github.com/kurapatijayaram/aa135e83388439f7a16a77d1509e9d6f

AngularJS(2.x/4.x) component:
https://gist.github.com/kurapatijayaram/0a2e4ded7806c190d6f77f9e739dca46


14 comments:

  1. It seems you are so busy in last month. The detail you shared about your work and it is really impressive that's why i am waiting for your post because i get the new ideas over here and you really write so well.

    Selenium training in Chennai
    Selenium training in Bangalore
    Selenium training in Pune
    Selenium Online training
    Selenium training in bangalore

    ReplyDelete
  2. You have provided a nice article, Thank you very much for this one. And I hope this will be useful for many people. And I am waiting for your next post keep on updating these kinds of knowledgeable things
    Java Training in Chennai
    Java Training in Coimbatore
    Java Training in Bangalore

    ReplyDelete
  3. Great blog..You have clearly explained about the concept..Step by step explanation is too good to understand..Its very useful for me to understand.thanks lot!!
    android training in chennai

    android online training in chennai

    android training in bangalore

    android training in hyderabad

    android Training in coimbatore

    android training

    android online training


    ReplyDelete