PrimeNG DataTable: Mastering Lazy Loading with Virtual Scrolling for 50k+ Rows
PrimeNG’s DataTable can choke on 50k rows. Enable lazy loading and virtual scrolling to fetch only the visible slice, keeping memory low and scrolling smooth. Follow this guide for a concrete example, trade‑offs, and best practices.
18 Sept 2026, 04:44 UTC

Problem: Rendering 50,000 Rows in a Browser
When a DataTable is bound to a large dataset, the browser must create a DOM node for every row. With 50,000 rows, this consumes hundreds of megabytes of memory and makes scrolling sluggish. The UI feels frozen even when the data itself is tiny. The root cause is that PrimeNG’s default DataTable loads all rows into the component’s value array before rendering.
Thesis: Lazy Loading + Virtual Scrolling Solves the Bottleneck
PrimeNG lets you fetch only the slice of data that is currently visible. By enabling [lazy] and [virtualScroll], the table asks the server for a page of rows whenever the user scrolls, and it renders only those rows. The rest of the dataset never reaches the browser, keeping memory usage low and scrolling smooth.
Lazy Loading Fundamentals
- onLazyLoad – Event fired by the table when a new data slice is needed. The event payload contains
first(index of the first row to load) androws(page size). - pageSize – Number of rows the table requests per API call. It should match the
rowsvalue sent by the event. - totalRecords – Total number of rows in the dataset. The server must return this count so the table can calculate the scroll height. If omitted, virtual scrolling cannot determine the maximum scroll extent.
- fixed row height – Virtual scroll relies on a constant row height. If rows have variable height, the scroll bar jumps or shows gaps.
Implementation Walk‑through
- Add PrimeNG to the Angular project
ng add primeng npm install primeicons - Configure the DataTable in the template
<p-table [value]="data" [lazy]="true" [virtualScroll]="true" [rowHeight]="50" [totalRecords]="totalRecords" (onLazyLoad)="loadData($event)"> <ng-template pTemplate="header"> <tr><th>ID</th><th>Name</th><th>Status</th></tr> </ng-template> <ng-template pTemplate="body" let-row> <tr><td>{{row.id}}</td><td>{{row.name}}</td><td>{{row.status}}</td></tr> </ng-template> </p-table> - Implement the loadData method in the component
import { Component } from '@angular/core'; import { LazyLoadEvent } from 'primeng/api'; @Component({ selector: 'app-large-table', templateUrl: './large-table.component.html' }) export class LargeTableComponent { data: any[] = []; totalRecords: number = 0; loadData(event: LazyLoadEvent) { const page = Math.floor(event.first! / event.rows!); const pageSize = event.rows!; // Example: replace with real HTTP call this.mockApi(page, pageSize).then(result => { this.data = result.items; this.totalRecords = result.total; }); } // Mock API returning 100 items per page async mockApi(page: number, pageSize: number) { const total = 50000; const items = Array.from({ length: pageSize }, (_, i) => ({ id: page * pageSize + i + 1, name: `Item ${page * pageSize + i + 1}`, status: i % 2 === 0 ? 'Active' : 'Inactive' })); return { items, total }; } } - Verify the behaviour
- Open Chrome DevTools Network tab and confirm that each scroll triggers a single API call with the correct
pageandpageSizeparameters. - Check the console for the
onLazyLoadevent to ensurefirstandlastindices are correct. - Scroll through the table; the UI should remain responsive and the number of DOM nodes should stay around
pageSize+ a few buffer rows.
- Open Chrome DevTools Network tab and confirm that each scroll triggers a single API call with the correct
Trade‑offs and Limitations
- Network Latency – Each scroll can trigger a request. If the backend is slow, the user may see a flash of empty rows. Show a
loadingspinner or a placeholder row to mitigate this. - Variable Row Height – Virtual scroll assumes a fixed
rowHeight. If rows contain images or expand on click, consider disabling virtual scroll or usingscrollHeightwithscrollablerows. - Server‑Side Pagination API – The backend must expose an endpoint that accepts
offsetandlimit(orpageandsize) and returnstotalcount. Inconsistent totals break scroll calculations. - State Persistence – When navigating away and back, the table does not remember the last scroll position. Store
firstandrowsin a service if needed.
Actionable Take‑away
To keep your PrimeNG DataTable performant with massive datasets:
- Always enable
[lazy]and[virtualScroll]for tables larger than a few thousand rows. - Provide a consistent
totalRecordsfrom the server. - Use a fixed
rowHeightor ascrollHeightthat matches the tallest row. - Show a loading indicator during
onLazyLoadcallbacks to hide latency spikes. - Test with real network conditions (e.g., Chrome throttling) to ensure UX stays smooth.
With these steps, you’ll transform a sluggish 50k‑row table into a responsive, memory‑efficient component that scales to millions of records.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.