Pagination
A standalone pagination bar around two-way [(pageIndex)]/[(pageSize)] models — 0-based, with pageSize: 0 meaning "all items" (the grid pager's contracts, kept aligned by design). The numeric window holds a constant width: ellipsis slots count toward maxButtons, so the bar never jitters while paging, and an ellipsis never hides a single page.
No WAI-ARIA APG pagination pattern exists, so the markup is composed from primitives: a <nav> landmark named by messages, real buttons with aria-current="page" on the active page, and the info range in an aria-live="polite" region. Keyboard is the native Tab order — every control is a native element, and the APG defines no arrow-key behavior to invent. Rendering several bars on one page? Give each a distinct paginationLabel via [messages] — landmarks must be unique.
Getting started
Numeric buttons in a constant-width window with real ellipsis markers. pageChanged reports previousPageIndex and fires only on user interaction — programmatic writes and auto-clamps update the model silently.
Page index: 0
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgePagination } from '@oge-ui/navigation';
@Component({
selector: 'demo-root',
imports: [OgePagination],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- pageIndex is 0-BASED and two-way; the numeric window keeps a
CONSTANT width — ellipsis slots count toward maxButtons (default 7), so
paging from the first page to the middle never jitters the layout. An
ellipsis never hides a single page (it would render the page instead). -->
<oge-pagination [(pageIndex)]="page" [itemCount]="total" [pageSize]="20" />
`,
})
export class Demo {
protected readonly page = signal(0);
protected readonly total = 400;
}Page sizes and info
Presence of pageSizes shows the selector; 'all' commits pageSize: 0. showInfo renders the {from}–{to} of {itemCount} range from messages, in a polite live region. Shrinking the count auto-clamps pageIndex.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgePagination } from '@oge-ui/navigation';
@Component({
selector: 'demo-root',
imports: [OgePagination],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- Presence of pageSizes shows the selector (no separate boolean);
'all' commits pageSize 0 — "all items on one page", the grid pager's
exact contract. Changing the size re-clamps pageIndex before the rich
pageSizeChanged event reports it. -->
<oge-pagination
[(pageIndex)]="page"
[(pageSize)]="size"
[itemCount]="total"
[pageSizes]="[10, 20, 50, 'all']"
[showInfo]="true"
/>
`,
})
export class Demo {
protected readonly page = signal(0);
protected readonly size = signal(20);
protected readonly total = 97;
}Jump controls
First/last buttons and the jump-to-page input are opt-in — the numeric rails already render both ends, so the defaults stay lean. The jump input displays 1-based, commits on Enter/change and clamps into range.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgePagination } from '@oge-ui/navigation';
@Component({
selector: 'demo-root',
imports: [OgePagination],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- First/last jump buttons (Material's showFirstLastButtons) and
the jump-to-page input (PrimeNG's showJumpToPageInput; Kendo's
type: 'input'): 1-based display, Enter/blur commit, clamped into range,
display re-synced after a clamp. -->
<oge-pagination
[(pageIndex)]="page"
[itemCount]="total"
[pageSize]="10"
[showFirstLastButtons]="true"
[showJumpToPageInput]="true"
/>
`,
})
export class Demo {
protected readonly page = signal(41);
protected readonly total = 1000;
}Unknown total
Without itemCount the total is unknown: only prev/next and a "Page N" indicator render, and next never disables — the component cannot know the end. Clamp pageIndex at the app level when the server reports the last page.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgePagination } from '@oge-ui/navigation';
@Component({
selector: 'demo-root',
imports: [OgePagination],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- itemCount undefined = the server cannot count. Only prev/next
and a "Page N" indicator render, and NEXT NEVER DISABLES — the component
cannot know the end. Clamp pageIndex yourself when the server reports the
last page. -->
<oge-pagination [(pageIndex)]="page" [pageSize]="20" />
`,
})
export class Demo {
protected readonly page = signal(3);
}Adaptive display
displayMode: 'adaptive' collapses to the compact N / M indicator below the container threshold (compactBelow, default 480px) — measured against the bar's own box, never the window. 'compact' forces the indicator.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgePagination } from '@oge-ui/navigation';
@Component({
selector: 'demo-root',
imports: [OgePagination],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- displayMode 'adaptive' switches to the compact "N / M"
indicator below the container threshold (config compactBelow, default
480px) — measured against the pagination's OWN box via ResizeObserver,
never the window (core's resolveMenubarCompact). 'compact' forces the
indicator unconditionally. -->
<div style="max-width: 320px">
<oge-pagination
[(pageIndex)]="page"
[itemCount]="total"
[pageSize]="20"
displayMode="adaptive"
/>
</div>
`,
})
export class Demo {
protected readonly page = signal(4);
protected readonly total = 400;
}Configuration
Every string — the <nav> label, button aria labels, the info template — lives in OgePaginationMessages: override globally via provideOgePaginationConfig() or per instance via [messages]. maxButtons, displayMode and compactBelow carry config defaults too.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgePagination, provideOgePaginationConfig } from '@oge-ui/navigation';
// Application- or component-scoped defaults; per-instance [messages] wins.
// providers: [
// provideOgePaginationConfig({
// maxButtons: 9,
// messages: { pageSizeLabel: 'Sayfa başına', info: '{itemCount} kayıttan {from}–{to}' },
// }),
// ]
@Component({
selector: 'demo-root',
imports: [OgePagination],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<oge-pagination [(pageIndex)]="page" [itemCount]="total" [showInfo]="true" />
`,
})
export class Demo {
protected readonly page = signal(0);
protected readonly total = 250;
}