Server‑Side Pagination in PrimeNG DataTable: Lazy Loading Made Easy
PrimeNG DataTable’s lazy loading turns the UI into a thin client that asks the server for a slice of data. This guide explains the event contract, backend expectations, a concrete Angular example, and common pitfalls to avoid.
03 Dec 2025, 06:15 UTC

Why you need server‑side pagination
When a PrimeNG DataTable renders thousands of rows, the browser stalls, the UI freezes, and the network bandwidth explodes. The usual fix is to let the server do the heavy lifting: fetch only the rows that the user can see at a time. PrimeNG’s lazy flag turns the component into a thin client that asks the backend for a slice of data whenever the user changes page, sorts, or filters.
Thesis: A clear contract between DataTable and the API
With lazy=true, the table emits a LazyLoadEvent that contains four key properties:
first– the zero‑based index of the first row to loadrows– how many rows to fetch (page size)sortField– the column used for sortingsortOrder– 1 for ascending, –1 for descending
The backend must accept these values, apply them to the query, and return two things:
- A JSON array of the requested rows
- The total number of records (so the pager knows how many pages exist)
1. The LazyLoadEvent payload
When the user jumps to page 3 of a table that shows 20 rows per page, PrimeNG sends a GET request that looks like this:
GET /api/products?first=40&rows=20&sortField=price&sortOrder=1
To inspect this network call in Chrome, open DevTools → Network, and filter by api/products. You should see the query parameters exactly as shown.
2. Backend contract: query and response
Below is a minimal Express‑style handler that satisfies the contract. Replace the /* ... */ with your database logic.
app.get('/api/products', async (req, res) => {
const first = parseInt(req.query.first, 10) || 0;
const rows = parseInt(req.query.rows, 10) || 20;
const sortField = req.query.sortField || 'id';
const sortOrder = req.query.sortOrder === '-1' ? -1 : 1;
// Example: replace with your DB query
const totalRecords = await db.countProducts();
const items = await db.getProducts({
offset: first,
limit: rows,
orderBy: sortField,
orderDir: sortOrder === 1 ? 'ASC' : 'DESC',
});
res.json({ totalRecords, items });
});
PrimeNG expects the JSON shape to be either:
{ "totalRecords": 123, "items": [ /* rows */ ] }– the default- or a custom shape if you configure
paginatorTemplateaccordingly.
3. Wiring it in the Angular component
In your component, set lazy to true and bind the onLazyLoad event. The event handler should call the service that hits the API and assign the result to products and totalRecords.
export class ProductTableComponent {
products: any[] = [];
totalRecords = 0;
loading = false;
loadProducts(event: LazyLoadEvent) {
this.loading = true;
this.productService.getProducts(event).subscribe(data => {
this.products = data.items;
this.totalRecords = data.totalRecords;
this.loading = false;
});
}
}
Template snippet
<p-table [value]="products" [lazy]="true" (onLazyLoad)="loadProducts($event)"
[paginator]="true" [rows]="20" [totalRecords]="totalRecords" [loading]="loading">
<p-column field="id" header="ID" sortable></p-column>
<p-column field="name" header="Name" sortable></p-column>
<p-column field="price" header="Price" sortable></p-column>
</p-table>
4. Common pitfalls and trade‑offs
- Large page sizes – If you set
rowsto 200 or more, the database must return that many rows per request, which can strain memory and slow the UI. Keeprowsbetween 10 and 50 for most use cases. - Unindexed sort fields – Sorting on a column that isn’t indexed forces a full table scan on each request. Index the
sortFieldcolumns you expose to the UI. - Version changes – Newer PrimeNG releases may rename or add properties to
LazyLoadEvent. Keep an eye on the changelog and update your request handling accordingly. - totalRecords accuracy – If the backend returns an incorrect
totalRecords, the pager will misbehave. Verify by comparing the count of all rows in the database with the value returned.
Actionable checklist
- Enable lazy mode in the
p-tableand set a sensiblerowsvalue. - Implement a backend endpoint that accepts
first,rows,sortField, andsortOrder, and returnstotalRecords+items. - Index sorting columns on the database to keep query times low.
- Test the network call in DevTools to ensure the query parameters match what the backend expects.
- Validate the response by checking that
totalRecordsmatches the database count and that theitemsarray contains the correct slice.
Monitor performance – if users report slowness, consider reducing
rows or adding more indexes.
With this pattern, your PrimeNG DataTable can scale to millions of records without compromising the user experience.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.