import { Component } from '@angular/core';
import { WrPagination } from 'ngwr/pagination';
@Component({ imports: [WrPagination] })
export class MyComponent {}Pagination
Page navigation for a long list.
Installation
Import the class, and @use the entry point in your global styles: without it the component renders unstyled.
Basic usage
Bind [total] and [(page)].
<wr-pagination [total]="120" [(page)]="page" />Sizes
size: sm, md or lg.
<wr-pagination [total]="120" [(page)]="page" size="sm" />
<wr-pagination [total]="120" [(page)]="page" size="md" />
<wr-pagination [total]="120" [(page)]="page" size="lg" />Shape
square flattens the corners, for example inside a data table.
<wr-pagination [total]="120" [(page)]="page" shape="rounded" />
<wr-pagination [total]="120" [(page)]="page" shape="square" />With total + size changer
showTotal adds the range, showSizeChanger a page-size select, and align="end" moves it right.
<wr-pagination
[total]="320"
[(page)]="page"
[(pageSize)]="size"
showTotal
showSizeChanger
align="end"
/>Narrow container (container query)
With responsive, the strip turns into a compact pager when its own box is narrow.
<div style="width: 260px"><wr-pagination responsive [total]="120" [(page)]="page" /></div>Server-side paging
While total is 0, as during a request, the pager keeps the page you set and emits nothing for it.
<wr-pagination
[total]="total()"
[page]="page()"
(pageChange)="page.set($event)"
/>private readonly http = inject(HttpClient);
readonly page = signal(1);
readonly size = signal(10);
// The resource drops its value while a new page loads, so total reads 0.
private readonly result = rxResource({
params: () => ({ page: this.page(), size: this.size() }),
stream: ({ params }) => this.http.get<{ data: User[]; total: number }>('/api/users', { params }),
});
readonly rows = computed(() => this.result.value()?.data ?? []);
readonly total = computed(() => this.result.value()?.total ?? 0);Page size is the host's decision
Choosing a size only emits pageSizeChange. The page stays as it is: reset it yourself if you want.
<wr-pagination
showSizeChanger
[total]="total()"
[page]="page()"
(pageChange)="page.set($event)"
[pageSize]="size()"
(pageSizeChange)="onSizeChange($event)"
/>onSizeChange(next: number): void {
this.size.set(next);
this.page.set(1);
}Keeping the last good total
To keep the strip steady during a request, hold the last total in a linkedSignal.
readonly total = linkedSignal<number | undefined, number>({
source: () => this.result.value()?.total,
computation: (next, previous) => next ?? previous?.value ?? 0,
});Keyboard, roles and accessible names
Every cell is a tab stop; Enter or Space activates it.
label,prevLabel,nextLabelanditemsPerPageLabeloverride the text from the i18n catalog.- In end-to-end tests, use
getByRole('button', { name: 'Go to page 2' }); the current page hasaria-current="page". - In unit tests, use
WrPaginationHarnessfromngwr/pagination/testing.
API
| Name | Description | Type | Default |
|---|---|---|---|
page | Currently displayed page (1-based). Two-way bindable. page rather than currentPage since v14, so this and <wr-table> — which renders one of these in its own footer — spell the two concepts they share one way each. It also names the output: a model() called X forces (XChange), so the old name produced (currentPageChange) against the table's (pageChange). | number | 1 |
pageSize | Items per page. Two-way bindable. | number | 10 |
total | Total item count across all pages. | number | 0 |
pageSizeOptions | Options shown in the page-size dropdown. | readonly number[] | [10, 20, 50, 100] |
showSizeChanger | Render the page-size dropdown. | boolean | false |
showTotal | Render the "X–Y of Z" total label. | boolean | false |
align | Horizontal alignment. | WrPaginationAlign | 'start' |
size | Size variant — cascades to every internal button. | WrPaginationSize | 'sm' |
shape | Cell corner treatment. | WrPaginationShape | 'rounded' |
disabled | Disable interaction. | boolean | false |
responsive | Collapse to a compact ‹ page / total › pager when the control's own box is too narrow for the full numbered strip (a container query on its own width, not the viewport). | boolean | false |
prevLabel | Previous-page button aria-label. Falls back to pagination.prev. | string | null | null |
nextLabel | Next-page button aria-label. Falls back to pagination.next. | string | null | null |
itemsPerPageLabel | "Items per page" label. Falls back to pagination.itemsPerPage. | string | null | null |
label | Accessible name for the role="navigation" host. Falls back to pagination.label. | string | null | null |
CSS variables
Set these on the component's selector or a wrapper, not on :root: the defaults are declared on ngwr/pagination's own element.
| Variable | Default | Declared on |
|---|---|---|
--wr-pagination-cap-radius | var(--wr-border-radius-sm) | .wr-pagination |
--wr-pagination-cell | 1.75rem only under .wr-pagination--sm — unset elsewhere | .wr-pagination--sm +2 variant overrides |
--wr-pagination-cell-padding-x | var(--wr-space-sm) | .wr-pagination |
--wr-pagination-current-font-size | var(--wr-text-sm) | .wr-pagination |
--wr-pagination-current-padding-x | var(--wr-space-sm) | .wr-pagination |
--wr-pagination-font-family | var(--wr-font-family-base) | .wr-pagination |
--wr-pagination-inner-gap | var(--wr-space-lg) | .wr-pagination |
--wr-pagination-nav-gap | var(--wr-space-xs) | .wr-pagination |
--wr-pagination-padding-block | var(--wr-space-sm) | .wr-pagination |
--wr-pagination-total-font-size | var(--wr-text-sm) | .wr-pagination |
Dependencies
See also
Component TableRows of data with sorting, filtering, paging and custom cells.Component ListA list of rows with leading, body and trailing slots.Component Virtual ScrollA long list of same-height rows that renders only the rows on screen.Component ButtonA button for an action or a link.Component SelectPick one or several values from a list, with search and free-text tags.