PrimeNG DataTable Lazy Loading: Cut Load Time, Keep Pagination in Sync
PrimeNG DataTable lazy loading cuts initial load time by fetching only the rows the user needs. Learn how to wire the component, translate events into server requests, and avoid common pitfalls.
02 Jul 2026, 15:56 UTC

Why Lazy Loading Matters for Big Tables
When a table contains thousands of rows, rendering the whole dataset at once hurts both browser memory and the first‑page load time. PrimeNG’s DataTable offers a lazy mode that only pulls the rows the user actually needs. This keeps the UI snappy and reduces the load on the backend.
Thesis: Lazy loading works only when the DataTable, the Angular service, and the server API stay in lock‑step.
Pulling data lazily is not a feature you enable and forget. The component emits a request payload that the server must interpret, and the server must return a payload that the component can consume. A mismatch anywhere breaks pagination, sorting, or filtering.
1. Wire the DataTable for Lazy Mode
Set lazy="true" and bind (onLazyLoad) to a handler. The handler receives a LazyLoadEvent object that contains pagination, sorting, and filter information.
// app.component.html
<p-table [value]="rows" [lazy]="true" (onLazyLoad)="loadData($event)" [paginator]="true" [rowsPerPageOptions]="[10,20,50]" [totalRecords]="totalRecords">
<ng-template pTemplate="header">
<tr>
<th pSortableColumn="name">Name<p-sortIcon field="name">Age<p-sortIcon field="age">
Permissions: The component runs in the browser. No special permissions required. The totalRecords input must be updated by the server response.
2. Capture the Load Event in the Component
// app.component.ts
export class AppComponent {
rows: any[] = [];
totalRecords = 0;
constructor(private dataService: DataService) {}
loadData(event: LazyLoadEvent) {
// Debounce to avoid a request on every keystroke
this.dataService.getData(event).subscribe(resp => {
this.rows = resp.data;
this.totalRecords = resp.totalRecords;
});
}
}
Here LazyLoadEvent includes first (offset), rows (limit), sortField, sortOrder, and filters. The service turns those into query parameters.
3. Translate the Event into a Server Request
Below is a typical Angular service that builds a GET URL with query parameters. The server is expected to understand these parameters and return a JSON payload with data and totalRecords.
// data.service.ts
@Injectable({ providedIn: 'root' })
export class DataService {
constructor(private http: HttpClient) {}
getData(event: LazyLoadEvent): Observable<{ data: any[]; totalRecords: number }> {
const params = new HttpParams()
.set('offset', event.first?.toString() ?? '0')
.set('limit', event.rows?.toString() ?? '10')
.set('sortField', event.sortField ?? '')
.set('sortOrder', event.sortOrder?.toString() ?? '0');
if (event.filters) {
Object.keys(event.filters).forEach(key => {
const filter = event.filters[key];
if (filter.value) {
params = params.set(`filter[${key}]`, filter.value);
}
});
}
return this.http.get<{ data: any[]; totalRecords: number }>('/api/users', { params });
}
}
Risk: If the server responds with an incorrect totalRecords, the paginator will misbehave. Always calculate this on the backend from the full dataset size.
4. Server‑Side API Contract
The API must accept the parameters and return a JSON object like:
{
"data": [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 35}
],
"totalRecords": 1245
}
Optional: Include a meta object to echo back sorting or filtering details if you need it on the client.
Concrete Example: A Minimal Express Server
Below is a Node/Express route that demonstrates the expected logic. It uses an in‑memory array for simplicity.
// server.js
const express = require('express');
const app = express();
const users = generateLargeUserArray(2000); // Assume this function exists
app.get('/api/users', (req, res) => {
const offset = parseInt(req.query.offset) || 0;
const limit = parseInt(req.query.limit) || 10;
const sortField = req.query.sortField || 'name';
const sortOrder = parseInt(req.query.sortOrder) || 1;
let result = [...users];
result.sort((a, b) => {
if (a[sortField] < b[sortField]) return -1 * sortOrder;
if (a[sortField] > b[sortField]) return 1 * sortOrder;
return 0;
});
const paged = result.slice(offset, offset + limit);
res.json({ data: paged, totalRecords: users.length });
});
app.listen(3000, () => console.log('Server running on :3000'));
Check: Open the browser dev tools, go to the Network tab, and confirm that the first request only pulls 10 rows, and subsequent page changes trigger new requests with updated offset and limit parameters.
Trade‑Offs and Limitations
- Client‑side vs Server‑side: Lazy mode turns off built‑in client‑side filtering and sorting. You must provide UI controls that trigger
onLazyLoadwith the correct parameters. - Accessibility: Because rows are loaded on demand, screen readers may miss content until it is scrolled into view. Consider adding a “load more” button for assistive users.
- SEO: Search engines crawl the initial page only. If the table holds critical content, provide a static pre‑rendered version or include the full dataset in a
scripttag. - Over‑requesting: If you fire a request on every keystroke, the backend can be overwhelmed. Debounce input or set a minimum delay of 300 ms.
- State consistency: The
totalRecordsvalue must always match the true size of the dataset. If it changes while a user is paginating, the paginator may jump unexpectedly.
Actionable Checklist
- Enable
lazy="true"on thep-tableand bind(onLazyLoad). - Implement a service that translates
LazyLoadEventinto query parameters. - Ensure the backend accepts
offset,limit,sortField,sortOrder, andfilter[…]and returnsdataandtotalRecords. - Debounce filter inputs and validate that the paginator updates correctly.
- Test the flow using the browser dev tools: confirm that only the requested page is fetched and that the total record count drives the paginator.
- Review accessibility and SEO implications for your use case.
By following this pattern, you can keep the initial load fast, avoid sending the entire dataset to the client, and still give users a responsive, paginated experience.
Next Steps
- Integrate this pattern into your existing Angular app and measure the performance difference with Chrome DevTools.
- Consider adding server‑side caching for repeated queries to further improve latency.
- Explore PrimeNG’s
virtualScrollfeature if you need infinite scrolling instead of pagination.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.