import type { BooleanInput, NumberInput } from '@angular/cdk/coercion';
import {
booleanAttribute,
ChangeDetectionStrategy,
Component,
computed,
input,
model,
numberAttribute,
untracked,
} from '@angular/core';
import { HlmSelectImports } from '@spartan-ng/styles/select';
import { classes } from '@spartan-ng/styles/utils';
import { createPageArray, outOfBoundCorrection } from './hlm-numbered-pagination';
import { HlmPagination } from './hlm-pagination';
import { HlmPaginationContent } from './hlm-pagination-content';
import { HlmPaginationEllipsis } from './hlm-pagination-ellipsis';
import { HlmPaginationItem } from './hlm-pagination-item';
import { HlmPaginationLink } from './hlm-pagination-link';
import { HlmPaginationNext } from './hlm-pagination-next';
import { HlmPaginationPrevious } from './hlm-pagination-previous';
@Component({
selector: 'hlm-numbered-pagination-query-params',
imports: [
HlmPagination,
HlmPaginationContent,
HlmPaginationItem,
HlmPaginationPrevious,
HlmPaginationNext,
HlmPaginationLink,
HlmPaginationEllipsis,
HlmSelectImports,
],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
{{ totalItems() }}
total items |
{{ _lastPageNumber() }}
pages
@for (pageSize of _pageSizesWithCurrent(); track pageSize) {
{{ pageSize }}
}
`,
})
export class HlmNumberedPaginationQueryParams {
/**
* The current (active) page.
*/
public readonly currentPage = model.required();
/**
* The number of items per paginated page.
*/
public readonly itemsPerPage = model.required();
/**
* The total number of items in the collection. Only useful when
* doing server-side paging, where the collection size is limited
* to a single page returned by the server API.
*/
public readonly totalItems = input.required({
transform: numberAttribute,
});
/**
* The URL path to use for the pagination links.
* Defaults to '.' (current path).
*/
public readonly link = input('.');
/**
* The number of page links to show.
*/
public readonly maxSize = input(7, {
transform: numberAttribute,
});
/**
* Show the first and last page buttons.
*/
public readonly showEdges = input(true, {
transform: booleanAttribute,
});
/**
* The page sizes to show.
* Defaults to [10, 20, 50, 100]
*/
public readonly pageSizes = input([10, 20, 50, 100]);
protected readonly _pageSizesWithCurrent = computed(() => {
const pageSizes = this.pageSizes();
return pageSizes.includes(this.itemsPerPage())
? pageSizes // if current page size is included, return the same array
: [...pageSizes, this.itemsPerPage()].sort((a, b) => a - b); // otherwise, add current page size and sort the array
});
protected readonly _isFirstPageActive = computed(() => this.currentPage() === 1);
protected readonly _isLastPageActive = computed(
() => this.currentPage() === this._lastPageNumber(),
);
protected readonly _lastPageNumber = computed(() => {
if (this.totalItems() < 1) {
// when there are 0 or fewer (an error case) items, there are no "pages" as such,
// but it makes sense to consider a single, empty page as the last page.
return 1;
}
return Math.ceil(this.totalItems() / this.itemsPerPage());
});
protected readonly _pages = computed(() => {
const correctedCurrentPage = outOfBoundCorrection(
this.totalItems(),
this.itemsPerPage(),
this.currentPage(),
);
if (correctedCurrentPage !== this.currentPage()) {
// update the current page
untracked(() => this.currentPage.set(correctedCurrentPage));
}
return createPageArray(
correctedCurrentPage,
this.itemsPerPage(),
this.totalItems(),
this.maxSize(),
);
});
constructor() {
classes(() => 'flex items-center justify-between gap-2 px-4 py-2');
}
}