Implementing Server‑Side Pagination, Sorting, and Filtering with Clarity Data Grid in Angular
Learn how to offload paging, sorting, and filtering to a server‑side API while keeping the Clarity Data Grid responsive in Angular.
29 Jan 2026, 23:30 UTC

Problem: Loading Large Datasets Makes the Grid Unresponsive
When a Clarity Data Grid (clr-datagrid) is bound to a large collection, the browser must render every row at once. This can cause slow initial loads, jerky scrolling, and excessive memory use. Moving the work of paging, sorting, and filtering to the server keeps the UI lightweight and ensures the grid only displays the data the user actually needs.
Thesis: By handling the grid’s change events and adjusting request parameters, you can delegate paging, sorting, and filtering to a backend API while keeping the Angular component simple and declarative.
1. Set Up the Grid with Server‑Side Hooks
The Clarity grid exposes three events that correspond to the user’s actions:
(clrDgChange)– fired when the page size or current page changes.(clrDgSort)– fired when a column header is clicked to sort.(clrDgFilter)– fired when the filter text is updated.
Bind these events to methods in your component and store the resulting values in local variables that will be sent to the API.
<clr-datagrid
[clrDgPageSize]="pageSize"
[(clrDgPage)]="currentPage"
(clrDgChange)="onPageChange($event)"
(clrDgSort)="onSortChange($event)"
(clrDgFilter)="onFilterChange($event)"
[clrDgLoading]="loading"
>
<clr-dg-column>Name</clr-dg-column>
<clr-dg-column>Email</clr-dg-column>
<clr-dg-column>Role</clr-dg-column>
<clr-dg-row *clrDgItems="let item of data">
<clr-dg-cell><{{ item.name }}</clr-dg-cell>
<clr-dg-cell><{{ item.email }}</clr-dg-cell>
<clr-dg-cell><{{ item.role }}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pager>
<clr-dg-page-size [clrDgPageSizeOptions]="[5, 10, 20]"></clr-dg-page-size>
<clr-dg-page-size></clr-dg-page-size>
<clr-dg-page-item *clrDgPageItems="let p"><{{ p }}</clr-dg-page-item>
<clr-dg-page-item *clrDgPageItems="let p"><{{ p }}</clr-dg-page-item>
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
2. Translate UI Events into API Parameters
In the component class, maintain objects for pagination, sorting, and filtering. Each event handler updates the relevant object and triggers a data fetch.
import { Component } from '@angular/core';
import { ClarityDatagridService } from './clarity-datagrid.service';
interface PageEvent {
pageSize: number;
page: number;
}
interface SortEvent {
sortBy: string;
sortAsc: boolean;
}
interface FilterEvent {
filters: { [key: string]: string };
}
@Component({
selector: 'app-user-grid',
templateUrl: './user-grid.component.html'
})
export class UserGridComponent {
data: any[] = [];
loading = false;
// UI bound values
pageSize = 10;
currentPage = 1;
// Internal state for the API
sort: SortEvent = { sortBy: 'name', sortAsc: true };
filters: FilterEvent = { filters: {} };
constructor(private svc: ClarityDatagridService) {
this.loadData();
}
onPageChange({ pageSize, page }: PageEvent) {
this.pageSize = pageSize;
this.currentPage = page;
this.loadData();
}
onSortChange({ sortBy, sortAsc }: SortEvent) {
this.sort = { sortBy, sortAsc };
this.loadData();
}
onFilterChange(filters: { [key: string]: string }) {
this.filters = { filters };
// Reset to first page when filters change
this.currentPage = 1;
this.loadData();
}
private loadData() {
this.loading = true;
const params = {
limit: this.pageSize,
offset: (this.currentPage - 1) * this.pageSize,
sortBy: this.sort.sortBy,
sortDir: this.sort.sortAsc ? 'asc' : 'desc',
...this.filters.filters
};
this.svc.fetchUsers(params).subscribe({
next: (resp) => {
this.data = resp.items;
// Assume the API returns totalCount for the pager
// (handled via [clrDgTotalItems] if you expose it)
this.loading = false;
},
error: (err) => {
console.error('Failed to load users', err);
this.loading = false;
}
});
}
}
3. Backend Contract Example
The service below shows how the Angular HTTP client maps the UI state to query parameters. Adjust the endpoint and parameter names to match your API.
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
interface UserResponse {
items: any[];
totalCount: number;
}
@Injectable({ providedIn: 'root' })
export class ClarityDatagridService {
private apiUrl = '/api/users';
constructor(private http: HttpClient) {}
fetchUsers(params: {
limit: number;
offset: number;
sortBy?: string;
sortDir?: 'asc' | 'desc';
[key: string]: any;
}): Observable {
let httpParams = new HttpParams()
.set('limit', params.limit.toString())
.set('offset', params.offset.toString());
if (params.sortBy) {
httpParams = httpParams.set('sortBy', params.sortBy)
.set('sortDir', params.sortDir);
}
Object.keys(params).forEach(key => {
if (!['limit', 'offset', 'sortBy', 'sortDir'].includes(key)) {
httpParams = httpParams.set(key, params[key]);
}
});
return this.http.get(this.apiUrl, { params: httpParams });
}
}
Worked Example: Switching Pages and Sorting
Imagine the user clicks the “Next” button on the pager while the grid is sorted by email ascending.
- The
(clrDgChange)handler receives{ pageSize: 10, page: 2 }and updatescurrentPage. loadData()builds params:limit=10, offset=10, sortBy=email, sortDir=asc.- The service sends a GET request like
/api/users?limit=10&offset=10&sortBy=email&sortDir=asc. - The backend returns rows 11‑20 ordered by email.
- The grid updates with the new rows; the pager shows “Page 2 of X”.
If the user then clicks the “Name” column header to sort descending, the (clrDgSort) handler updates the sort object, triggers another request with sortBy=name&sortDir=desc, and the grid re‑orders the already‑fetched page.
Trade‑offs and Limitations
- Backend must supply total count. Without
totalCountthe pager cannot compute the number of pages, showing a generic “…” or incorrect page numbers. Verify this by checking the API response includes a field liketotalCountortotalRecords. - Rapid interactions generate many requests. If users type quickly in a filter box or click pages fast, each keystroke or click fires a request. Mitigate with debouncing (e.g., wait 300 ms after the last keystroke) or caching recent pages.
- State synchronization. If the backend allows concurrent updates, the grid may show stale data. Consider using optimistic UI updates or re‑fetching after a mutation.
Actionable Checklist
- Add the three event bindings to
clr-datagridin your template. - Create component properties for pagination, sorting, and filtering; update them in the event handlers.
- Implement a service method that converts those properties into HTTP query parameters.
- Call the service on every change and assign the returned items to the grid’s data source.
- Ensure your API returns both the slice of data and a total record count.
- Test: open Chrome DevTools → Network, click a page button, confirm a request with correct
limitandoffsetappears and the grid updates without a full reload. - Verify sorting changes the
sortByandsortDirparameters and the displayed order updates after the response. - If you notice excessive requests, add debouncing to the filter handler or cache recent pages.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.