Simple Pagination in Lightning
When dealing with many records or in fact any list of records, its always a good practice to add some elements of pagination and limit the volume of records displayed.
This will optimise the performance and will reduce a chance of hitting limits when record volumes are high.
We can control the records displayed both from the client side and the server side.
But handling a large number of records on the client side seems to be consuming much CPU, so this would only be ideal for a small chunk of records – I like it till about 200 records depends on the size.
So Server side would be my go to if I’m planning to deal with larger number of records.
Apex offers a Simple way to paginate using SOQL statement with LIMIT and OFFSET clauses.
In Order to paginate we need to know 3 main elements :
- Total Number of Records
- Records per Page – How many records to be displayed on each page – To be set as the limit of records to retrieved.
- Index – to be used to offset the records on User paginate action.
On the Apex Controller I would run 2 Queries:
- Get Record count
- Get Records
private static final String OBJECT_NAME = 'Account'; private static final Set<String> FIELDS = new Set<String>{ 'Id', 'Name'}; @AuraEnabled(Cacheable=true) public static Map<String,Object> getRecords( String pageSize, String offsetIndex ){ // Get Total records String countQuery = 'SELECT count() FROM ' + OBJECT_NAME + ' LIMIT ' + RECORDS_LIMIT; Integer recordsCount = Database.countQuery(countQuery); // get Records String recordsQuery = 'SELECT ' + String.join( new List<String>(FIELDS),',') + ' FROM ' + OBJECT_NAME + ' LIMIT :pageSize OFFSET :offsetIndex'; List<SObject> records = Database.query(recordsQuery); return new Map<String,Object>{ 'records' => records, 'totalCount' => recordsCount }; }
On our Lightning Web component – Records Page will show the records
import { LightningElement, api, wire, track } from 'lwc'; import getRecords from '@salesforce/apex/MyController.getRecords'; export default class RecordsPage extends LightningElement { offsetIndex = 0; recordsPerPage = 5; // wired records data @wire(getRecords, { 'offsetIndex': '$offsetIndex', 'pageSize': '$recordsPerPage' }) wiredResults({ data, error }) { if(data){ this.records = data.records; this.totalRecords = data.totalCount; } else if (error) { console.error('error getting data', error ); } } onPaginate(event) { this.recordsPerPage = parseInt(event.detail.perpage); this.offsetIndex = parseInt(event.detail.index); } }
HTML – Parent
<template> <template if:true={records.length} for:each={records} for:item="record" > <div>{record.Name}</div> </template> <!-- PAGINATION --> <c-pagination index={offsetIndex} per-page={recordsPerPage} total={totalRecords} onpagechange={onPaginate}> </c-pagination> </template>
The pagination child component:
This component will simply display the control buttons for the pagination.
- Allow to paginate Next and Previous page
- Show Current Records per Page and Total Records
- Allow to switch the number of records per page
Basically to control the index when going Next/Prev we are simply adding or subtracting the number of records per page from our offset index.
- When we hit the Total Records => its the end.
- When Index is Zero => We are at the Start of our data records.
JS – #pagination.js
import { LightningElement, api } from 'lwc'; const PAGE_SIZE = ['5', '10', '15']; export default class Pagination extends LightningElement { @api index; @api total; @api get perPage() { return this.recordsPerPage; } set perPage(value) { this.recordsPerPage = parseInt(value); } currentIndex = 0; recordsPerPage = 5; label = { next: 'NEXT', prev:'PREV' } get totalRecords() { return this.total; } get isStart() { return this.index === 0; } get isEnd() { return (this.index + this.recordsPerPage) >= this.total; } get pageSizeOptions() { let pageOptions = PAGE_SIZE.map(opt => ({ value: parseInt(opt), label: opt })); // Add All records Option pageOptions.push({ value: this.total, label: 'ALL' }); return pageOptions; } get currentPageSize() { return parseInt(this.index + this.recordsPerPage) > this.total ? this.total : parseInt(this.index + this.recordsPerPage); } onChangePageSize(event) { this.recordsPerPage = parseInt(event.detail.value); this.publishPageChange(); } onNextPage() { this.currentIndex = this.index + this.recordsPerPage; this.publishPageChange(); } onPreviousPage() { this.currentIndex = this.index - this.recordsPerPage > 0 ? this.index - this.recordsPerPage : 0; this.publishPageChange(); } publishPageChange() { this.dispatchEvent( new CustomEvent('pagechange', { detail: { index: this.currentIndex, perpage: this.recordsPerPage }, })); } }
HTML – #pagination.html
<template> <!-- PAGINATION --> <div class="slds-grid slds-p-vertical_small"> <div> <!-- PREV --> <lightning-button label={label.prev} title={label.prev} name="prev" variant="inverse" onclick={onPreviousPage} disabled={isStart}> </lightning-button> <!-- NEXT --> <lightning-button class="slds-p-horizontal_small" label={label.next} title={label.next} name="next" variant="inverse" onclick={onNextPage} disabled={isEnd}></lightning-button> </div> <div class="slds-align--absolute-center"> <span class="slds-text-title_bold"> {currentPageSize} / {totalRecords} </span> </div> <div class="slds-col_bump-left slds-size_2-of-12"> <!-- PAGE SIZE --> <lightning-combobox class="slds-text-body_small" dropdown-alignment="auto" name="pageSize" label="Page Size" variant="label-hidden" value={recordsPerPage} options={pageSizeOptions} onchange={onChangePageSize} ></lightning-combobox> </div> </div> </template>