
What happens to a Lightning Component that displays a list of data without pagination?
It is probably doomed to suffer from performance problems and who wants to build a component that is doomed? Certainly not you, right?
It’s possible that Salesforce might release a pagination component at some point in the future, but until they do, you will need to roll out your own. Fortunately, it is not too terribly complicated and in this post I will walk you through how to do it.
To begin, I must give credit where credit is due and acknowledge that the paginator component I am using in this post is almost identical to the one used in the Dreamhouse application (which if you have not checked out, you really need to do so).
The markup code for the Paginator component is as follows:

And the Controller code looks like this:
({
previousPage : function(component) {
var pageChangeEvent = component.getEvent("pagePrevious");
pageChangeEvent.fire();
},
nextPage : function(component) {
var pageChangeEvent = component.getEvent("pageNext");
pageChangeEvent.fire();
}
})
Additionally, I use a component event called PageChange which looks like this:

Ok, so I have a component that currently renders a list of race data. To make it work with the paginator component through, I will have to make a few changes to both the markup, controller and helper, along with the Apex Controller it references.
The new version of the markup looks like this:

The Modified controller (seen below) now includes two new actions named onPagePrevious and onPageNext and these are referenced in the Paginator component.
({
doInit : function(component, event, helper) {
helper.getRaces(component);
},
handleAddToRaces : function(component, event, helper) {
helper.addToRaces(component, event);
},
onPagePrevious: function(component, event, helper) {
var page = component.get("v.page") || 1;
page = page - 1;
helper.getRaces(component, page);
},
onPageNext: function(component, event, helper) {
var page = component.get("v.page") || 1;
page = page + 1;
helper.getRaces(component, page);
}
})
And the getRaces function in the Helper file has been modified to look like this:
// Added new parameter called page to pass in the page number
// If no page parameter is passed in, it will just default to
// a value of 1 and this is the case on the initial call for
// the doInit action
getRaces : function(component, page) {
var action = component.get('c.getRacesDB');
// Added the pageSize variable which is passed in as an attribute
var pageSize = component.get("v.pageSize");
// Added code to set the new parameters that are now passed on
// to the Apex Controller Code
action.setParams({"pageSize": pageSize,
"pageNumber": page || 1
});
action.setCallback(this, function(response) {
var state = response.getState();
if (component.isValid() && state === "SUCCESS") {
// Instead of just returning all the data
// as a list, I will get back a result
// object which is defined in the Apex Controller
var result = response.getReturnValue();
component.set("v.races", result.races);
// Added code to set the values for the page,
// total and pages attributes
component.set("v.page", result.page);
component.set("v.total", result.total);
component.set("v.pages", Math.ceil(result.total/pageSize));
} else {
//Handle errors
var errors = response.getError();
if (errors) {
if (errors[0] && errors[0].message) {
component.set("v.errorMsg", errors[0].message);
component.set("v.isError", true);
}
} else {
component.set("v.errorMsg", "unknown error, response state: " +
response.getState());
component.set("v.isError", true);
}
}
});
$A.enqueueAction(action);
},
The last thing to do is to modify the code in the Apex Server Controller, which will now look like this:
public with sharing class ListRacesController {
@AuraEnabled
// Changed the return value from List to PageResult
// which is defined in the inner class below Also added two
// new parameters for the pageSize and pageNumber
public static PageResult getRacesDB(Decimal pageSize, Decimal pageNumber) {
// Added new variables
Integer pSize = (Integer)pageSize;
Integer offset = ((Integer)pageNumber - 1) * pSize;
Integer totalRows = 0;
// Instead of just returning a List of races from a single
// query, we are now returning a PageResult
PageResult res = new PageResult();
res.pageSize = pSize;
res.page = (Integer) pageNumber;
// The first query is used to fetch the data that will
// be displayed and it will be limited to return just
// the data for the particular page it needs to render
res.races = [SELECT Id, Name, DateTime__c, Location__c,
Attended__c, Type__c, Results__c FROM Race__c
ORDER BY DateTime__c desc
LIMIT :pSize OFFSET :offset];
// We have to do a separate aggregate query to get
// the total number of records since this will be
// used to compute the offset
res.total = [SELECT Count() FROM Race__c];
return res;
}
// Added PageResult class which defines the
// results returned from the getRaces method
public class PageResult {
@AuraEnabled
public Integer pageSize { get;set; }
@AuraEnabled
public Integer page { get;set; }
@AuraEnabled
public Integer total { get;set; }
@AuraEnabled
public List races { get;set; }
}
}
And that’s it. I now have a component that will by default only display 5 races at a time and allow the user to move between the pages using the arrow buttons.
And now I can rest – assured that my component (which honestly could use some other improvements), will not perform miserably when the number of races eventually climbs to a very high number.
Pretty neat, right?
Want to learn about other improvements? Well, the next one is to add caching to this same component and believe it or not, I can do it with a single line of code. Check out this post for more info. And stay tuned because this blog will continue to feature lightning best practices such as these.
EDIT: Below is the requested markup and code for the inner RaceV2 component, which actually includes the individual race data.
First, the Markup:

And now the Helper Resource:
({
updateRace :function(component) {
var race = component.get("v.race");
console.log("Calling updateRace");
var action = component.get("c.updateRaceDB");
action.setParams({ "race" : race });
action.setCallback(this, function(response) {
var state = response.getState();
if (component.isValid() && state === "SUCCESS") {
console.log("Race successfully updated");
} else if (state === "ERROR") {
var errors = response.getError();
if (errors) {
if (errors[0] && errors[0].message) {
console.log("Error message: " + errors[0].message);
}
} else {
console.log("Unknown error");
}
} else {
console.log("Action State returned was: " + state);
}
});
$A.enqueueAction(action);
}
})
If you found this article useful, you might want to checkout my latest course on Pluralsight titled, Lightning Component Development Best Practices, where I talk about pagination and a lot more.