typescript
69 lines · 9 steps
Paginating a CDK table with Angular signals
A standalone Angular component pairs the CDK table with signals so pagination stays reactive and derived state recomputes itself.
Explained by
highlit
1import { Component, computed, signal } from '@angular/core';
2import { CdkTableModule } from '@angular/cdk/table';
3import { CdkScrollableModule } from '@angular/cdk/scrolling';
4
5interface Invoice {
6 id: string;
7 customer: string;
8 amount: number;
9 status: 'paid' | 'pending' | 'overdue';
10}
11
12@Component({
13 selector: 'app-invoice-table',
14 standalone: true,
15 imports: [CdkTableModule, CdkScrollableModule],
16 template: `
17 <cdk-table [dataSource]="page()" [trackBy]="trackById" class="invoice-table">
18 <ng-container cdkColumnDef="customer">
19 <cdk-header-cell *cdkHeaderCellDef>Customer</cdk-header-cell>
20 <cdk-cell *cdkCellDef="let row">{{ row.customer }}</cdk-cell>
21 </ng-container>
22
23 <ng-container cdkColumnDef="amount">
24 <cdk-header-cell *cdkHeaderCellDef>Amount</cdk-header-cell>
25 <cdk-cell *cdkCellDef="let row">{{ row.amount | currency }}</cdk-cell>
26 </ng-container>
27
28 <ng-container cdkColumnDef="status">
29 <cdk-header-cell *cdkHeaderCellDef>Status</cdk-header-cell>
30 <cdk-cell *cdkCellDef="let row" [attr.data-status]="row.status">{{ row.status }}</cdk-cell>
31 </ng-container>
32
33 <cdk-header-row *cdkHeaderRowDef="columns"></cdk-header-row>
34 <cdk-row *cdkRowDef="let row; columns: columns"></cdk-row>
35 </cdk-table>
36
37 <nav class="pager">
38 <button (click)="prev()" [disabled]="pageIndex() === 0">Prev</button>
39 <span>Page {{ pageIndex() + 1 }} of {{ totalPages() }}</span>
40 <button (click)="next()" [disabled]="pageIndex() >= totalPages() - 1">Next</button>
41 </nav>
42 `,
43})
44export class InvoiceTableComponent {
45 readonly columns = ['customer', 'amount', 'status'];
46 readonly pageSize = 20;
47
48 invoices = signal<Invoice[]>([]);
49 pageIndex = signal(0);
50
51 totalPages = computed(() => Math.max(1, Math.ceil(this.invoices().length / this.pageSize)));
52
53 page = computed(() => {
54 const start = this.pageIndex() * this.pageSize;
55 return this.invoices().slice(start, start + this.pageSize);
56 });
57
58 trackById(_: number, row: Invoice): string {
59 return row.id;
60 }
61
62 next(): void {
63 this.pageIndex.update((i) => Math.min(i + 1, this.totalPages() - 1));
64 }
65
66 prev(): void {
67 this.pageIndex.update((i) => Math.max(i - 1, 0));
68 }
69}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Deriving state with computed signals keeps pagination in sync without manual recalculation.
- 2trackBy lets the CDK table reuse DOM rows across data changes instead of rebuilding them.
- 3signal.update takes the current value, making bounded increments like page navigation safe and self-contained.
Related explainers
typescript
type Semver = { major: number; minor: number; patch: number;
Parsing and comparing semver strings in TypeScript
parsing
regular-expressions
comparison
Intermediate
9 steps
python
from flask import Blueprint, jsonify from marshmallow import Schema, fields, validate, EXCLUDE from webargs.flaskparser import use_args
Validating query params in Flask with webargs
validation
schema
query-building
Intermediate
10 steps
typescript
import { Component } from '@angular/core'; import { trigger, transition,
Staggered list animations in Angular
animations
stagger
enter-leave
Intermediate
10 steps
typescript
type Handler = (event: KeyboardEvent) => void; interface Binding { combo: string;
Building a keyboard shortcut manager in TypeScript
event-handling
normalization
closures
Intermediate
7 steps
typescript
type MatchSegment = { text: string; matched: boolean; };
Splitting text into highlighted match segments
regex
string-matching
text-highlighting
Intermediate
8 steps
go
type PostCursor struct { CreatedAt time.Time ID int64 }
Keyset pagination with cursors in Go
pagination
keyset-cursor
database
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/paginating-a-cdk-table-with-angular-signals-explained-typescript-8786/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.