Read Large Lists in SharePoint Framework

Overview

 
Reading large lists always has been a challenge for SharePoint developers. This is one of those instances where the on-premises environment is appreciated by SharePoint developers, as SharePoint administrators can come to their rescue by adjusting the query threshold limit in Central Administration. The other commonly used approaches from the developer are to limit the items in view, index the columns, etc. This workaround reduces the pain to a certain extent but not fully. Unfortunately, we do not have much administration liberty in SharePoint Online to play around with threshold limits.
 
In this article, we will revise the threshold concept and various approaches to read the large lists programmatically in SharePoint Framework.
 
 

Threshold

 
Let's quickly revise the term Threshold. To build information architecture for SharePoint, it is important to understand the SharePoint online limitations and boundaries. Which says, the list items limit is 30 Million. That's very very huge.
 
Every time you access a list or document library, a search query happens behind the scenes that return all the results. For example, if you have 50,000 items in a list, when you try to show them in a single view or programmatically retrieve those in a single query, you won't be able to get more than 5,000 items. We can say the list/library is facing the 5,000 item limit threshold. The threshold limit with SharePoint Online is still 5,000.
 
Do not confuse the threshold of 5,000 items with the list capacity which is 30 Million.
 

Get around with Threshold

 
PnP JS is the popular library amongst SPFx developers for SharePoint operations. It provides some cool methods to get around the threshold issues.
 
Example# 1: Basic usage
  1. pnp.sp.web.lists.getByTitle("BigList").items.getAll().then((allItems: any[]) => {  
  2.      // how many did we get  
  3.      console.log(allItems.length);  
  4. });  
Example# 2: Set page size
  1. pnp.sp.web.lists.getByTitle("BigList").items.getAll(4000).then((allItems: any[]) => {  
  2.      // how many did we get  
  3.      console.log(allItems.length);  
  4. });  
Example# 3: Use select and top. top will set page size and override the any value passed to getAll
  1. pnp.sp.web.lists.getByTitle("BigList").items.select("Title").top(4000).getAll().then((allItems: any[]) => {  
  2.      // how many did we get  
  3.      console.log(allItems.length);  
  4. });  
Example# 4: Use filter as a supported odata operation 
  1. pnp.sp.web.lists.getByTitle("BigList").items.select("Title").filter("Title eq 'Test'").getAll().then((allItems: any[]) => {  
  2.      // how many did we get  
  3.      console.log(allItems.length);  
  4. });  
But, this will likely fail on large lists.
 

Custom Implementation

 
Here is another approach to get read the large list with paging implementation.
 
Consider we have a SharePoint list named “LargeList” with below schema:
 
Field Name Type
Title Single line of text
Description Multiple lines of text
Category Single line of text
Quantity Number
  
The model to represent above list will be as below:
  1. export interface ILargeListItem {  
  2.      Title: string;  
  3.      Description: string;  
  4.      Category: string;  
  5.      Quantity: number;  
  6. }  
Let us define our approach to handle large list:
  • We will define the page size as 5000 for example (maximum threshold limit). Please note, SharePoint internally fetches the items in the batch of 100 items.
  • The number of requests we will have to read entire large list will be: Number of list items / page size.
  • We will make asynchronous requests to read the list items in batch.
  • Wait for all asynchronous requests to finish
Implement a genetic method getPageListItems which returns items with paging, starting with index passed as an argument.
  1. $skiptoken=Paged=TRUE%26p_ID=` + (index * Constants.Page_Size + 1)  
The getPageListItems method implementation will look as follows:
  1. private getPageListItems(listTitle: string, index: number): Promise<ILargeListItem[]> {  
  2.     return new Promise<ILargeListItem[]>((resolve, reject): void => {  
  3.     let requestUrl = this.context.pageContext.web.absoluteUrl  
  4.         + `/_api/web/Lists/GetByTitle('` + listTitle + `')/items`  
  5.         + `?$skiptoken=Paged=TRUE%26p_ID=` + (index * Constants.Page_Size + 1)  
  6.         + `&$top=` + Constants.Page_Size  
  7.         + `&$select=ID,Title,Description,Category,Quantity`;  
  8.   
  9.     this.context.spHttpClient.get(requestUrl, SPHttpClient.configurations.v1)  
  10.     .then((response: SPHttpClientResponse) => {  
  11.         response.json().then((responseJSON: any) => {    
  12.             resolve(responseJSON.value);    
  13.           });    
  14.         });  
  15.     });   
  16. }   
Implement a method to get the latest item id. This will be our maximum limit to read the items.
  1. public getLatestItemId(listTitle: string): Promise<number> {  
  2.     return new Promise<number>((resolve: (itemId: number) => void, reject: (error: any) => void): void => {  
  3.         sp.web.lists.getByTitle(listTitle)  
  4.             .items.orderBy('Id'false).top(1).select('Id').get()  
  5.             .then((items: { Id: number }[]): void => {  
  6.                 if (items.length === 0) {  
  7.                     resolve(-1);  
  8.                 }  
  9.                 else {  
  10.                     resolve(items[0].Id);  
  11.                 }  
  12.             }, (error: any): void => {  
  13.                 reject(error);  
  14.             });  
  15.     });  
  16. }  
The next step is to implement method the main method to make asynchronous requests to read the list items in batch.
  1. public async getLargeListItems(listTitle: string): Promise<ILargeListItem[]> {  
  2.     var largeListItems: ILargeListItem[] = [];  
  3.   
  4.     return new Promise<ILargeListItem[]>(async (resolve, reject) => {  
  5.         // Array to hold async calls  
  6.         const asyncFunctions = [];  
  7.   
  8.         this.getLatestItemId(listTitle).then(async (itemCount: number) => {  
  9.             for (let i = 0; i < Math.ceil(itemCount / Constants.Page_Size); i++) {  
  10.                 // Make multiple async calls  
  11.                 let resolvePagedListItems = () => {  
  12.                     return new Promise(async (resolve) => {  
  13.                         let pagedItems:ILargeListItem[] = await this.getPageListItems(listTitle, i);  
  14.                         resolve(pagedItems);  
  15.                     })  
  16.                 };  
  17.                 asyncFunctions.push(resolvePagedListItems());  
  18.             }  
  19.   
  20.                        // Wait for all async calls to finish  
  21.             const results: any = await Promise.all(asyncFunctions);  
  22.             for (let i = 0; i < results.length; i++) {  
  23.                 largeListItems = largeListItems.concat(results[i]);  
  24.             }  
  25.   
  26.             resolve(largeListItems);  
  27.         });  
  28.     });  
  29. }  

Summary

Reading large lists always has been a challenge for SharePoint developers. There are various approaches to read the large lists programmatically in SharePoint Framework. We explored the options to read large lists with PnP JS as well a custom implementation with paging approach.