Handling Million-Row Datasets with PrimeNG Lazy Loading
Stop crashing your Angular app with massive datasets. Learn how to implement PrimeNG DataTable lazy loading to shift pagination and sorting to the server.
29 Jun 2026, 15:38 UTC

The Performance Wall in Large Tables
Loading 10,000 rows into an Angular component creates a massive memory footprint and slows the browser's DOM rendering to a crawl. While PrimeNG's Table component handles small lists effortlessly, relying on client-side pagination means the browser must first download the entire dataset before the user can see the first page. This leads to long initial load times and potential application crashes on mobile devices.
The solution is Lazy Loading. By shifting the burden of pagination, sorting, and filtering to the server, the browser only ever handles the specific slice of data currently visible to the user.
Implementing the Lazy Loading Pattern
To enable server-side operations, you must set the [lazy] property to true. This tells PrimeNG to stop trying to manage the data internally and instead emit an event whenever the user interacts with the paginator or sort headers.
The core of this implementation is the onLazyLoad event. This event provides a LazyLoadEvent object containing the first (the index of the first record) and rows (the number of records per page) properties. These values must be mapped to your backend API parameters—typically offset and limit.
Crucial State Management
When lazy is enabled, two properties become critical for the UI to function:
totalRecords: The table cannot calculate how many pages to show based on the current array length (which is only one page). You must explicitly set this to the total count returned by your database.loading: Because data is fetched asynchronously, setting this boolean totrueduring the API call triggers the built-in loading overlay, preventing users from triggering multiple simultaneous requests.
Worked Example: Server-Side Integration
Assume an Angular environment using PrimeNG 16+ and a backend API that accepts offset and limit.
// component.ts
import { Component } from '@angular/core';
import { LazyLoadEvent } from 'primeng/api';
import { DataService } from './data.service';
@Component({
selector: 'app-user-table',
templateUrl: './user-table.component.html'
})
export class UserTableComponent {
users: any[] = [];
totalRecords: number = 0;
loading: boolean = false;
constructor(private dataService: DataService) {}
loadUsers(event: LazyLoadEvent) {
this.loading = true;
const params = {
offset: event.first || 0,
limit: event.rows || 10,
sortField: event.sortField,
sortOrder: event.sortOrder
};
this.dataService.getUsers(params).subscribe({
next: (response) => {
this.users = response.data;
this.totalRecords = response.total;
this.loading = false;
},
error: () => this.loading = false
});
}
}
<!-- component.html -->
<p-table [value]="users" [lazy]="true" (onLazyLoad)="loadUsers($event)" [paginator]="true" [rows]="10" [totalRecords]="totalRecords" [loading]="loading" [rowsPerPageOptions]="[10,20,50]">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name">Name <p-sortIcon column="name"/></th>
<th>Email</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-user>
<tr>
<td>{{user.name}}</td>
<td>{{user.email}}</td>
</tr>
</ng-template>
</p-table>
Trade-offs and Limitations
While lazy loading solves memory issues, it introduces a dependency on network latency. Every page change or sort action now requires a round-trip to the server. If your API is slow, the table will feel sluggish despite the loading overlay.
Additionally, be cautious with rowsPerPageOptions. While server-side pagination handles the data, rendering 1,000 rows in a single page can still cause browser lag due to the number of DOM elements created. It is recommended to cap the maximum rows per page at 100.
Verification Checklist
To ensure your implementation is working correctly, perform these checks:
- Network Inspection: Open Browser DevTools. Changing a page should trigger a single XHR request with updated offset parameters, not a request for the entire dataset.
- Paginator State: Verify that the total page count in the UI matches the
totalRecordsvalue returned by your API. - Empty State: Ensure that if the API returns an empty array but a total count of 0, the table displays the empty message rather than hanging in a loading state.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.