Implementing Primeng DataTable Lazy Loading for Efficient Remote Pagination
Learn how to implement PrimeNG DataTable lazy loading for remote pagination, from configuring the component to handling LazyLoadEvent, verifying the flow, and handling edge cases.
12 Jun 2026, 06:40 UTC

What You’ll Achieve
By the end of this guide you’ll have a PrimeNG DataTable that requests only the data slice the user wants, reducing bandwidth and load time for large datasets. The component will:
- Emit a
LazyLoadEventwhenever the user changes page, sort, or filter. - Send
first(offset) androws(page size) to your API. - Render the returned rows and update the total record count for the paginator.
Prerequisites
- Angular 17+ project with PrimeNG 15+ installed.
- Backend endpoint that accepts
firstandrowsquery parameters and returns JSON in the shape:{ "data": [...], "totalRecords": 12345 } - Basic understanding of Angular services, HTTPClient, and RxJS.
Step‑by‑Step Procedure
- Configure the DataTable
In your component template, enable lazy loading and bind the
onLazyLoadevent.<p-table [value]="items" [lazy]="true" [totalRecords]="totalRecords" [paginator]="true" [rows]="pageSize" (onLazyLoad)="loadTable($event)" [sortField]="sortField" [sortOrder]="sortOrder"> <p-column field="name" header="Name" sortable></p-column> <p-column field="age" header="Age" sortable></p-column> </p-table>Key attributes:
[lazy]="true"tells PrimeNG to defer data loading.[totalRecords]="totalRecords"supplies the paginator with the overall count.[paginator]="true"enables the built‑in page controls.[rows]="pageSize"sets the default page size.
- Handle the LazyLoadEvent
In your component TS, implement
loadTableto call your service. PrimeNG’sLazyLoadEventnow has explicit fields:first,rows,sortField,sortOrder, and optionalfilters.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 { items: any[] = []; totalRecords = 0; pageSize = 25; sortField = 'name'; sortOrder = 1; // 1 for ascending, -1 for descending constructor(private dataService: DataService) {} loadTable(event: LazyLoadEvent) { console.log('LazyLoadEvent:', event); const params = { first: event.first ?? 0, rows: event.rows ?? this.pageSize, sortField: event.sortField ?? this.sortField, sortOrder: event.sortOrder ?? this.sortOrder }; this.dataService.getUsers(params).subscribe({ next: res => { this.items = res.data; this.totalRecords = res.totalRecords; }, error: err => { console.error('Failed to load table data', err); // recovery: show empty table or fallback data this.items = []; this.totalRecords = 0; } }); } }Notes:
- Always provide defaults for
firstandrowsto avoid undefined values when the table first renders. - Use the optional
filtersfield if you need server‑side filtering.
- Always provide defaults for
- Implement the Service
Use
HttpClientto hit your API. Pass query parameters as an object; Angular serializes them automatically.import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; export interface User { id: number; name: string; age: number; } export interface PaginatedResponse { data: User[]; totalRecords: number; } @Injectable({ providedIn: 'root' }) export class DataService { private readonly apiUrl = '/api/users'; constructor(private http: HttpClient) {} getUsers(params: { first: number; rows: number; sortField?: string; sortOrder?: number; }): Observable { const httpParams = new HttpParams({ fromObject: { first: params.first.toString(), rows: params.rows.toString(), ...(params.sortField ? { sortField: params.sortField } : {}), ...(params.sortOrder ? { sortOrder: params.sortOrder.toString() } : {}) } }); return this.http.get(this.apiUrl, { params: httpParams }); } } - Verify the Flow
Open Chrome DevTools → Network tab. Trigger a page change or sort. You should see a request similar to:
GET /api/users?first=25&rows=25&sortField=name&sortOrder=1Check the response body contains
dataandtotalRecords. If the table shows the correct rows and the paginator reflects the total, the integration is correct. - Handle Edge Cases
- Backend Failure: In the
errorcallback, clear the table and optionally display a toast message. - Changing Page Size: If you allow the user to change
rows, resettotalRecordsto 0 and re‑callloadTablewith the new page size. - Non‑Offset Pagination: If your API uses keyset or cursor pagination, map the returned cursor to
firstandrowsor implement a custom wrapper that translates PrimeNG events into your API calls.
- Backend Failure: In the
- Optional: Disable Lazy Loading for Small Datasets
If the dataset is under 500 rows, consider disabling lazy loading to avoid an extra round‑trip:
<p-table [value]="items" [lazy]="false" …>Load the entire dataset once in
ngOnInitand let PrimeNG handle pagination locally.
Expected Checks & Recovery
- Check that
totalRecordsmatches the backend count. If mismatched, the paginator will misbehave. - Verify that the DataTable’s
valuearray length never exceedsrowsunless the last page is partial. - In recovery, if
totalRecordsis zero after a load, reset the paginator to page 1 and show a friendly message.
Common Pitfalls
- Forgetting to set
[lazy]="true"causes the table to load all data at once. - Using
firstwithout addingrowsleads to incomplete slices. - Not returning
totalRecordsmakes the paginator unable to calculate total pages.
Wrap‑Up
Lazy loading in PrimeNG DataTable is a lightweight way to handle large datasets. By following the steps above you’ll have a responsive table that only fetches what the user needs, while keeping the server and client side in sync.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.