Run the CLI
Use the CLI to add the component to your project.
npx @ngzard/ui@latest add paginationPagination with page navigation, next and previous links.
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ZardPaginationModule } from '../pagination.module';
@Component({
selector: 'z-demo-pagination-default',
imports: [ZardPaginationModule, FormsModule],
standalone: true,
template: ` <z-pagination [zPageIndex]="currentPage" [zTotal]="5" [(ngModel)]="currentPage" /> `,
})
export class ZardDemoPaginationDefaultComponent {
protected currentPage = 2;
}
Use the CLI to add the component to your project.
npx @ngzard/ui@latest add paginationpnpm dlx @ngzard/ui@latest add paginationyarn dlx @ngzard/ui@latest add paginationbunx @ngzard/ui@latest add paginationCreate the component directory structure and add the following files to your project.
import {
booleanAttribute,
ChangeDetectionStrategy,
Component,
computed,
forwardRef,
input,
linkedSignal,
output,
ViewEncapsulation,
} from '@angular/core';
import { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import type { ClassValue } from 'clsx';
import {
paginationContentVariants,
paginationEllipsisVariants,
paginationItemVariants,
paginationNextVariants,
paginationPreviousVariants,
paginationVariants,
} from './pagination.variants';
import { mergeClasses } from '../../shared/utils/utils';
import { ZardButtonComponent } from '../button/button.component';
import { type ZardButtonVariants } from '../button/button.variants';
import { ZardIconComponent } from '../icon/icon.component';
@Component({
selector: 'z-pagination-content',
template: `
<div [attr.aria-label]="ariaLabel()" role="navigation" data-slot="pagination-content" [class]="classes()">
<ng-content />
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
exportAs: 'zPaginationContent',
})
export class ZardPaginationContentComponent {
readonly ariaLabel = input<string>('pagination-content');
readonly class = input<ClassValue>('');
protected readonly classes = computed(() => mergeClasses(paginationContentVariants(), this.class()));
}
@Component({
selector: 'z-pagination-item',
template: `
<div data-slot="pagination-item" [class]="classes()">
<ng-content />
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
exportAs: 'zPaginationItem',
})
export class ZardPaginationItemComponent {
readonly class = input<ClassValue>('');
protected readonly classes = computed(() => mergeClasses(paginationItemVariants(), this.class()));
}
@Component({
selector: 'z-pagination-button',
imports: [ZardButtonComponent],
standalone: true,
template: `
<button
z-button
data-slot="pagination-button"
[attr.aria-disabled]="zDisabled() || null"
[attr.data-disabled]="zDisabled() || null"
[attr.aria-current]="zActive() ? 'page' : undefined"
[attr.data-active]="zActive() || null"
[zType]="zType()"
[zSize]="zSize()"
[class]="class()"
(click)="handleClick()"
>
<ng-content />
</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
exportAs: 'zPaginationButton',
})
export class ZardPaginationButtonComponent {
readonly zDisabled = input(false, { transform: booleanAttribute });
readonly zActive = input(false, { transform: booleanAttribute });
readonly zSize = input<ZardButtonVariants['zSize']>();
readonly class = input<ClassValue>('');
readonly zClick = output<void>();
protected readonly zType = computed<ZardButtonVariants['zType']>(() => (this.zActive() ? 'outline' : 'ghost'));
handleClick() {
if (!this.zDisabled() && !this.zActive()) {
this.zClick.emit();
}
}
}
@Component({
selector: 'z-pagination-previous',
imports: [ZardPaginationButtonComponent, ZardIconComponent],
template: `
<z-pagination-button aria-label="Go to previous page" [class]="classes()" zSize="default">
<z-icon zType="chevron-left" />
<span class="hidden sm:block">Previous</span>
</z-pagination-button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
exportAs: 'zPaginationPrevious',
})
export class ZardPaginationPreviousComponent {
readonly class = input<ClassValue>('');
protected readonly classes = computed(() => mergeClasses(paginationPreviousVariants(), this.class()));
}
@Component({
selector: 'z-pagination-next',
imports: [ZardPaginationButtonComponent, ZardIconComponent],
template: `
<z-pagination-button aria-label="Go to next page" [class]="classes()" zSize="default">
<span class="hidden sm:block">Next</span>
<z-icon zType="chevron-right" />
</z-pagination-button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
exportAs: 'zPaginationNext',
})
export class ZardPaginationNextComponent {
readonly class = input<ClassValue>('');
protected readonly classes = computed(() => mergeClasses(paginationNextVariants(), this.class()));
}
@Component({
selector: 'z-pagination-ellipsis',
imports: [ZardIconComponent],
template: `
<z-icon zType="ellipsis" aria-hidden="true" role="presentation" />
<span class="sr-only">More pages</span>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
'[class]': 'classes()',
},
exportAs: 'zPaginationEllipsis',
})
export class ZardPaginationEllipsisComponent {
readonly class = input<ClassValue>('');
protected readonly classes = computed(() => mergeClasses(paginationEllipsisVariants(), this.class()));
}
@Component({
selector: 'z-pagination',
imports: [
ZardPaginationContentComponent,
ZardPaginationItemComponent,
ZardPaginationButtonComponent,
ZardIconComponent,
],
template: `
<z-pagination-content>
<z-pagination-item>
<z-pagination-button
aria-label="Go to previous page"
[zSize]="zSize()"
[zDisabled]="disabled() || currentPage() === 1"
(zClick)="goToPrevious()"
>
<z-icon zType="chevron-left" />
</z-pagination-button>
</z-pagination-item>
@for (page of pages(); track page) {
<z-pagination-item>
<z-pagination-button
[zSize]="zSize()"
[zActive]="page === currentPage()"
[zDisabled]="disabled()"
(zClick)="goToPage(page)"
>
{{ page }}
</z-pagination-button>
</z-pagination-item>
}
<z-pagination-item>
<z-pagination-button
aria-label="Go to next page"
[zSize]="zSize()"
[zDisabled]="disabled() || currentPage() === zTotal()"
(zClick)="goToNext()"
>
<z-icon zType="chevron-right" />
</z-pagination-button>
</z-pagination-item>
</z-pagination-content>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ZardPaginationComponent),
multi: true,
},
],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
'[class]': 'classes()',
},
exportAs: 'zPagination',
})
export class ZardPaginationComponent implements ControlValueAccessor {
readonly zPageIndex = input<number>(1);
readonly zTotal = input<number>(1);
readonly zSize = input<ZardButtonVariants['zSize']>();
readonly zDisabled = input(false, { transform: booleanAttribute });
readonly class = input<ClassValue>('');
readonly zPageIndexChange = output<number>();
protected readonly classes = computed(() => mergeClasses(paginationVariants(), this.class()));
protected readonly disabled = linkedSignal(() => this.zDisabled());
readonly currentPage = linkedSignal(this.zPageIndex);
readonly pages = computed<number[]>(() => Array.from({ length: Math.max(0, this.zTotal()) }, (_, i) => i + 1));
goToPage(page: number): void {
if (this.disabled()) return;
if (page !== this.currentPage() && page >= 1 && page <= this.zTotal()) {
this.currentPage.set(page);
this.zPageIndexChange.emit(page);
this.onChange(page);
this.onTouched();
}
}
goToPrevious() {
this.goToPage(this.currentPage() - 1);
}
goToNext() {
this.goToPage(this.currentPage() + 1);
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
private onChange: (value: number) => void = () => {};
// eslint-disable-next-line @typescript-eslint/no-empty-function
private onTouched: () => void = () => {};
writeValue(value: number): void {
this.currentPage.set(value);
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.disabled.set(isDisabled);
}
}
import { cva, type VariantProps } from 'class-variance-authority';
export const paginationContentVariants = cva('flex flex-row items-center gap-1');
export type ZardPaginationContentVariants = VariantProps<typeof paginationContentVariants>;
export const paginationItemVariants = cva('');
export type ZardPaginationItemVariants = VariantProps<typeof paginationItemVariants>;
export const paginationPreviousVariants = cva('gap-1 px-2.5 sm:pl-2.5');
export type ZardPaginationPreviousVariants = VariantProps<typeof paginationPreviousVariants>;
export const paginationNextVariants = cva('gap-1 px-2.5 sm:pr-2.5');
export type ZardPaginationNextVariants = VariantProps<typeof paginationNextVariants>;
export const paginationEllipsisVariants = cva('flex size-9 items-center justify-center');
export type ZardPaginationEllipsisVariants = VariantProps<typeof paginationEllipsisVariants>;
export const paginationVariants = cva('mx-auto flex w-full justify-center');
export type ZardPaginationVariants = VariantProps<typeof paginationVariants>;
import { NgModule } from '@angular/core';
import {
ZardPaginationButtonComponent,
ZardPaginationComponent,
ZardPaginationContentComponent,
ZardPaginationEllipsisComponent,
ZardPaginationItemComponent,
ZardPaginationNextComponent,
ZardPaginationPreviousComponent,
} from './pagination.component';
const components = [
ZardPaginationContentComponent,
ZardPaginationItemComponent,
ZardPaginationButtonComponent,
ZardPaginationPreviousComponent,
ZardPaginationNextComponent,
ZardPaginationEllipsisComponent,
ZardPaginationComponent,
];
@NgModule({
imports: components,
exports: components,
})
export class ZardPaginationModule {}
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ZardPaginationModule } from '../pagination.module';
@Component({
selector: 'z-demo-pagination-default',
imports: [ZardPaginationModule, FormsModule],
standalone: true,
template: ` <z-pagination [zPageIndex]="currentPage" [zTotal]="5" [(ngModel)]="currentPage" /> `,
})
export class ZardDemoPaginationDefaultComponent {
protected currentPage = 2;
}
import { Component, signal } from '@angular/core';
import { ZardPaginationModule } from '../pagination.module';
@Component({
selector: 'z-demo-pagination-custom',
imports: [ZardPaginationModule],
standalone: true,
template: `
<z-pagination-content ariaLabel="Page navigation">
<z-pagination-item>
<z-pagination-previous (click)="goToPrevious()" />
</z-pagination-item>
@for (page of pages(); track page) {
<z-pagination-item>
<z-pagination-button [zActive]="page === currentPage()" (click)="goToPage(page)">
{{ page }}
</z-pagination-button>
</z-pagination-item>
}
<z-pagination-item>
<z-pagination-next (click)="goToNext()" />
</z-pagination-item>
</z-pagination-content>
`,
})
export class ZardDemoPaginationCustomComponent {
readonly totalPages = 5;
readonly currentPage = signal(3);
readonly pages = signal<number[]>(Array.from({ length: this.totalPages }, (_, i) => i + 1));
goToPage(page: number) {
this.currentPage.set(page);
}
goToPrevious() {
if (this.currentPage() > 1) {
this.currentPage.update(p => p - 1);
}
}
goToNext() {
if (this.currentPage() < this.totalPages) {
this.currentPage.update(p => p + 1);
}
}
}