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'); } }