Use Case:
We have been using lightning-datatable to a great extent to show related records or similar functionality. But
ever wondered, what if the records shown using lightning-datatable are
comparatively too large.
Let's say, we are architecting a requirement which says to show all the
Accounts in the system with some basic related information. What will
be your first approach to meet this requirement? Well, there could be many
solutions but let's focus two prominent approaches:
- Pagination
- Infinite Loading
Though both have their own advantages, infinite loading is more promising if
we have to show the records and as the very idea of this blog is to know
about infinite loading, let's emphasize on infinite loading approach. But
how to achieve the infinite loading in lightning-datatable in LWC?
Implementation:
Infinite Loading Demystified:
The very idea behind the infinite loading in
lightning-datatable comes from the fact that when a user scrolls down to the end of the
results, it fetches more records to append to existing set of displayed
records. Assuming our use case of showing all Accounts in the system,
we are showing 20 account records at a time to a user and when user has
scrolled down all the way to the end of the records, next lot of 20 fresh
records are fetched, appended to existing records and displayed on UI and
hence making the total displayed records count to 40 and so on.
More in a nutshell, infinite scrolling enables you to load a subset of data
and then display more when users scroll to the end of the table. To enable
infinite scrolling, specify enable-infinite-loading and provide an
event handler using onloadmore on lightning-datatable. By
default, data loading is triggered when you scroll down to 20px from
the bottom of the table, but the offset can be changed using the
load-more-offset attribute. Please go through the lightning-datatable documentation to know more.
Infinite Loading Approaches: There are couple of ways to implement infinite loading. Below are the widely
used approaches.
- Using OFFSET: Typically this is the best approach for infinite loading if we know well in advance that the records being rendered by lightning-datatable are well under 2000 since we have certain limitations in using OFFSET in SOQL. To know more on how do we use OFFSET in SOQL and limitations, please go through the official documentation.
- Using Wrapper Class: If we are to implement the infinite loading for more than 2000 records, OFFSET is of no use because of its limitation in SOQL. We can definitely build our solution with Wrapper Class here and is more practical use case and solution approach if records are relatively large(>2000).
-
Using ID Field: As we all know,
ID fields are indexed fields and are auto-generated in some random
fashion. But they have a unique property, each time
an ID is generated and linked with record, the ASCII
value is greater than all previously generated ID. We can
simply use the last record ID in the table and retrieve the next
lot of fresh records with
ID > retrieved last record ID. This gives a true sense of having
infinite loading even if the records are greater than 50,000.(A
limitation otherwise in SOQL query rows limit).For example:
SELECT Id, Name, AccountNumber, Phone, CreatedDate FROM Account where id > 'LAST RECORD ID IN TABLE' LIMIT 20
This query will return new set of 20 records followed by previous record id in the Account Table.
Infinite Loading Implementation: For learning, we will focus on how do we use OFFSET in
implementing infinite loading. The other two are then self explainable if we
are good with the first and understand thoroughly.
Let's jump straight to the solution. The code itself is self explanatory, so
should be fairly quick to crack on.
-
Create an Apex Class RelatedListController: The purpose of
this class is to fetch the records on request basis. Each time a new
request(APEX call from LWC with onloadmore event) is made, it
returns the next 20 records from the offSetCount passed in to
the getRecords
method.
public class RelatedListController { @AuraEnabled public static List<Account> getRecords(Integer offSetCount) { return [SELECT Id, Name, AccountNumber, Phone, CreatedDate FROM Account LIMIT 20 OFFSET :offSetCount]; } }
-
Create a LWC component customDatatableDemo: Create LWC
component customDatatableDemo as given below.
i. customDatatableDemo.html: To enable infinite scrolling, specify enable-infinite-loading and provide an event handler using onloadmore on lightning-datatable. By default, data loading is triggered when you scroll down to 20px from the bottom of the table, but the offset can be changed using the load-more-offset attribute.
ii. customDatatableDemo.js: The onloadmore event handler retrieves more data when you scroll to the bottom of the table until there are no more data to load. To display a spinner while data is being loaded, set the isLoading property to true.<template> <lightning-card> <div class="slds-var-p-around_small"> <div> <span class="slds-form-element__label slds-text-title_bold slds-align_absolute-center"> Infinite Loading Example</span> </div> <div> <span class="slds-form-element__label slds-text-title_bold"> Total Records: {totalNumberOfRows}</span> </div> <div> <span class="slds-form-element__label slds-text-title_bold"> Displayed Records: {data.length}</span> </div> <div class="slds-box" style="height: 400px;"> <lightning-datatable key-field="Id" data={data} columns={columns} load-more-offset="20" onloadmore={handleLoadMore} enable-infinite-loading hide-checkbox-column show-row-number-column> </lightning-datatable> </div> {loadMoreStatus} </div> </lightning-card> </template>
While this example uses a fixed number to denote the total number of rows, you can also use the SOQL SELECT syntax with the COUNT() function to return the number of rows in the object in your Apex controller. Then, set the result on the totalNumberOfRows attribute during initialization.import { LightningElement } from 'lwc'; import { NavigationMixin } from 'lightning/navigation'; import getRecords from '@salesforce/apex/RelatedListController.getRecords'; const columns = [ { label: 'Account Name', fieldName: 'linkAccount', type: 'url', typeAttributes: { label: { fieldName: 'Name' }, target: '_blank' } }, { label: 'Account Number', fieldName: 'AccountNumber', type: 'text'}, { label: 'Phone', fieldName: 'Phone', type: 'text'}, { label: 'Created Date', fieldName: 'CreatedDate', type: 'text'} ]; export default class CustomDatatableDemo extends NavigationMixin( LightningElement ) { columns = columns; data = []; error; totalNumberOfRows = 200; // stop the infinite load after this threshold count // offSetCount to send to apex to get the subsequent result. 0 in offSetCount signifies for the initial load of records on component load. offSetCount = 0; loadMoreStatus; targetDatatable; // capture the loadmore event to fetch data and stop infinite loading connectedCallback() { //Get initial chunk of data with offset set at 0 this.getRecords(); } getRecords() { getRecords({offSetCount : this.offSetCount}) .then(result => { // Returned result if from sobject and can't be extended so objectifying the result to make it extensible result = JSON.parse(JSON.stringify(result)); result.forEach(record => { record.linkAccount = '/' + record.Id; }); this.data = [...this.data, ...result]; this.error = undefined; this.loadMoreStatus = ''; if (this.targetDatatable && this.data.length >= this.totalNumberOfRows) { //stop Infinite Loading when threshold is reached this.targetDatatable.enableInfiniteLoading = false; //Display "No more data to load" when threshold is reached this.loadMoreStatus = 'No more data to load'; } //Disable a spinner to signal that data has been loaded if (this.targetDatatable) this.targetDatatable.isLoading = false; }) .catch(error => { this.error = error; this.data = undefined; console.log('error : ' + JSON.stringify(this.error)); }); } // Event to handle onloadmore on lightning datatable markup handleLoadMore(event) { event.preventDefault(); // increase the offset count by 20 on every loadmore event this.offSetCount = this.offSetCount + 20; //Display a spinner to signal that data is being loaded event.target.isLoading = true; //Set the onloadmore event taraget to make it visible to imperative call response to apex. this.targetDatatable = event.target; //Display "Loading" when more data is being loaded this.loadMoreStatus = 'Loading'; // Get new set of records and append to this.data this.getRecords(); } }
Note: I have used here the imperative method to call APEX. You can also go with wire function call to APEX.
iii. customDatatableDemo.js-meta.xml:<?xml version="1.0" encoding="UTF-8"?> <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> <apiVersion>51.0</apiVersion> <isExposed>true</isExposed> <targets> <target>lightning__Tab</target> </targets> </LightningComponentBundle>
- Create a tab of the component created in step 2 to test our infinite loading in lighitning-datatable in LWC.
Conclusion:
Now as have understood the requirement and implemented the solution,
one thing to keep in mind is that when we define the height of
lightning-datatable container div element in LWC html file and
with enable-infinite-loading, the table will try to load more
records if the available height of the lightning-datatable thus rendered
with the records doesn't match to the given div component height. Ideally
the rendered lightning-datatable height should be greater than the container
div element. Think of assigning the height of container div dynamically if
you are not sure of records being flooded to lightning-datatable to display.
If you like this blog content and find inciteful, please comment and let me know.
This won't help in loading more than 2000 records
ReplyDeleteCan you give some more details about the method of using Wrapper Class? Thanks.
ReplyDeleteWould be nice if you could share more about the methods for loading 2,000+ records. Also would be nice to see the writing/code copied from the lightning-datatable documentation (https://developer.salesforce.com/docs/component-library/bundle/lightning-datatable/documentation) distinguished somehow from your original writing/code in this article.
ReplyDeleteI am facing a weird issue which might actually be there for everyone too. The infinite scrolling works fine and fresh data loads, but everytime the new data loads the vertical scroll bar jumps to the mid of the table. SO lets say if I scrolled to record number 50 and more data loads then I go back to somewhere around record number 25. Might not be a good UX.
ReplyDeleteDid you ever figure out this issue? It is incredibly infuriating and you are the only person I have seen post about it anywhere online.
Delete