ngx-signal-pagination
TypeScript icon, indicating that this package has built-in type declarations

0.0.5 • Public • Published

ngx-signal-pagination

Pagination for Angular, powered by signals 🚦

Features

  • All signals, no RxJS
  • Works with in-memory data as well as fetching data from server on page change
  • Keep it simple by using the predefined template
  • Take control by rolling with your own template
  • Page number is reflected in URL
    • Not using the RouterModule for this as it uses RxJS

Caveats

  • Page number is reflected in URL, but is not unique for all pagination controls. Having multiple pagination controls on one page is not currently supported.

Todos

  • Deploy a live demo site
  • Deploy an example project
  • Add something like 3 default templates
  • Default asynchronous template could use a better loading indicator
  • Improve type-safety with custom template. These let-... are currently of type any.
    <ng-template
      #paginationTemplate
      let-currentPage="currentPage"
  • Improve type-safety with asynchronous data retrieval. Your query should return this format for pagination to work:
    this.todoDal
      .getAll(this.pageChanges() ?? this.simpleQueryStringService.getPageFromQueryString() ?? 1)
      .then(res => ({
        pagedData: res.todos,
        currentPage: res.currentPage,
        nrOfPages: res.nrOfPages,
    }));
    That's currently not clear, the query parameter now accepts anything defined through injectQuery().
    query = input.required<CreateQueryResult<T, any>>();
    Due to angular-query's type narrowing, it's a bit tricky to customize this.

Installation

npm install ngx-signal-pagination

Usage

With in-memory data

First, the TypeScript bits - import the pagination component, define data and define pagination config:

@Component({
    selector: 'omg-awesome',
    imports: [NgxSignalPaginationComponent],
    templateUrl: './awesome.component.html',
})
export class AwesomeComponent {
    shows = [
        { title: 'Game of Thrones', rating: 9.5 },
        { title: 'Black Mirror', rating: 9.5 },
        /* ... */
    ];
    paging: PaginationOptions = { nrOfItemsPerPage: 5 };
}

Then you can either use the default template:

<ngx-signal-pagination [data]="shows" [config]="paging" #pagination />
<ul class="list-[square] ml-4">
    @for (show of pagination.pageData(); track show.title) {
        <li>{{ show.title }} gets a {{ show.rating }}</li>
    }
</ul>

Alternatively, you can roll your own template:

<ngx-signal-pagination [data]="shows" [config]="paging" #paginationCustomTemplate>
    <ng-template
        #paginationTemplate
        let-currentPage="currentPage"
        let-pages="pages"
        let-previous="previous"
        let-next="next"
        let-goTo="goTo"
    >
        <ol class="flex">
            <li class="p-3" (click)="previous()">previous</li>
                @for (page of pages(); track page) {
                    <li class="p-3 border-solid border-amber-300 border-b-2" [class.bg-amber-300]="page === currentPage()" (click)="goTo(page)">
                        {{ page }}
                    </li>
                }
            <li class="p-3" (click)="next()">next</li>
        </ol>
    </ng-template>
</ngx-signal-pagination>
<ul class="list-[square] ml-4">
    @for (show of paginationCustomTemplate.pageData(); track show.title) {
         <li>{{ show.title }} gets a {{ show.rating }}</li>
    }
</ul>

With asynchronous data retrieval

Asynchronous data retrieval works using the @tanstack/angular-query-experimental library. You'll need to define your query, as well as do a bit of page change handling*.

*I've tried exposing a signal from the pagination component, but this signal not always being there due to the rendering lifecycle made this approach quite cumbersome.

First, the TypeScript bits - import the async pagination component, define your and define pagination config:

@Component({
    selector: 'omg-awesome',
    imports: [NgxSignalPaginationAsyncComponent],
    templateUrl: './awesome.component.html',
})
export class AwesomeComponent {
	private todoDal = inject(TodoDal);
	private simpleQueryStringService = inject(SimpleQueryStringService);
    paging: PaginationOptions = { nrOfItemsPerPage: 5 };

    pageChanges = signal<number | null>(null);

    todoQuery = injectQuery(() => ({
        queryKey: ['todos', this.pageChanges() ?? this.simpleQueryStringService.getPageFromQueryString() ?? 1],
        queryFn: () => this.todoDal.getTodos(this.pageChanges() ?? this.simpleQueryStringService.getPageFromQueryString() ?? 1),
        placeholderData: prev => prev,
    }));

    goToPage(p: number) {
        this.pageChanges.set(p);
    }
}

In this scenario, my TodoDal looks like this:

@Injectable({ providedIn: 'root' })
export class TodoDal {
	#todos = [
		'#1 Clean the house',
		'#2 Eat food',
		'#3 Exercise',
		/* ... */
	];

	getTodos(page: number): Promise<{ pagedData: any[], currentPage: number; nrOfPages: number; }> {
		return new Promise(resolve => {
			setTimeout(() => {
				resolve({
					pagedData: this.#todos.slice((page - 1) * 5, page * 5),
					currentPage: page,
					nrOfPages: Math.ceil(this.#todos.length / 5),
				});
			}, 2000);
		});
	}
}

Then you can either use the default template:

<ngx-signal-async-pagination [query]="todoQuery" (pageChange)="goToPage($event)" [config]="paging" #asyncPagination />
<ul class="list-[square] ml-4">
    @for (todo of asyncPagination.pageData(); track todo) {
        <li>{{ todo }}</li>
    }
</ul>

Or roll a custom template as with the in-memory variant.

Package Sidebar

Install

npm i ngx-signal-pagination

Weekly Downloads

40

Version

0.0.5

License

MIT

Unpacked Size

48.8 kB

Total Files

11

Last publish

Collaborators

  • realjptenberge