Related: Components index · Table · Spartan UI Migration
src/app/shared/components/paginator/ — a signal-based replacement for MatPaginator, built from
Spartan Helm primitives (HlmButton, HlmSelect, HlmIcon, HlmTooltip). It deliberately mirrors
MatPaginator's public surface and its subtler behaviours, so migrating a page was find-and-replace
and so existing expectations still hold.
Usually paired with ll-table, but it has no dependency on it — it only needs a
length.
imports: [LlPaginatorImports],<ll-paginator [sticky]="true" [length]="dataSource.filteredData().length" />Wired to a table's data source in ngAfterViewInit (see Table):
readonly paginator = viewChild.required(Paginator);
ngAfterViewInit(): void {
this.dataSource.paginator = this.paginator();
}| Member | Type | Default | Notes |
|---|---|---|---|
length |
input.required<number> |
— | Total number of items. |
pageSizeOptions |
input<number[]> |
[10, 25, 50] |
Choices in the page-size picker. |
hidePageSize |
input<boolean> |
false |
Hides the "Rows per page" picker. |
showFirstLastButtons |
input<boolean> |
false |
Shows first/last jump buttons. |
disabled |
input<boolean> |
false |
Ignores navigation and page-size changes. |
sticky |
input<boolean> |
false |
Adds ll-paginator-sticky (see gotcha below). |
pageIndex |
model<number> |
0 |
Two-way. |
pageSize |
model<number> |
10 |
Two-way. |
page |
output<PageEvent> |
— | Emitted on any effective change. |
Read-only signals: pageCount, isFirstPage, isLastPage.
Methods: firstPage(), previousPage(), nextPage(), lastPage(), onPageSizeChange(size).
interface PageEvent {
pageIndex: number;
previousPageIndex?: number;
pageSize: number;
length: number;
}{ provide: PAGINATOR_DEFAULT_OPTIONS, useValue: { pageSize: 25, pageSizeOptions: [25, 50] } }PaginatorDefaultOptions accepts pageSize, pageSizeOptions, hidePageSize and
showFirstLastButtons — mirroring MAT_PAGINATOR_DEFAULT_OPTIONS. Inputs still win over the token.
These are the MatPaginator behaviours it reproduces on purpose. Each is covered by a test in
paginator.component.spec.ts; change one and a test should fail.
- Changing page size preserves the first visible item. At
pageSize10 on page 2 (items 20–29), switching topageSize5 lands on page 4 — not page 0. MirrorsMatPaginator._changePageSize. pageIndexauto-clamps when the data shrinks. AneffectpullspageIndexback in range whenpageCountdrops, so a filter that shortens the list cannot strand you on an empty page. MirrorsMatTableDataSource._updatePaginator.- Navigation clamps at both ends, and a no-op navigation emits nothing —
previousPage()on page 0 emits nopageevent. disabledblocks navigation and page-size changes, not just pointer events.pageCountismax(1, ceil(length / pageSize)), so an empty list still reports one page.
[sticky]="true" only adds the ll-paginator-sticky class (src/styles/_ll-paginator.scss), which
is plain CSS position: sticky. It therefore requires the nearest scrolling ancestor to have a
definite height and overflow-y-auto. For feature pages that ancestor is
<main hlmSidebarInset> (h-svh overflow-y-auto); in a Spartan dialog it is the scrolling middle
<div class="min-h-0 overflow-x-hidden overflow-y-auto">, bounded by the dialog's height cap. This is the
same trap described at length in Table → sticky gotcha
— check the ancestor before debugging the paginator.
page carries previousPageIndex and fires only when something actually changed. Consumers rely on
both facts; silently emitting on no-ops would cause redundant refetches.
src/app/features/spaces/tasks/tasks.component.html:149 — <ll-paginator [sticky]="true" [length]="dataSource.filteredData().length" />
bound to a TableDataSource. src/app/features/spaces/contents/shared/assets-select-dialog/ shows
the in-dialog variant.