DEV Community

Kedar Kulkarni
Kedar Kulkarni

Posted on

1 1

Simplifying Pagination in Angular with a Reusable Base Component

Our objective is to abstract the repetitive aspects of pagination—such as tracking the current page, page size, total items, and loading state—into a base component. This base component will provide a standardized way to handle pagination, which can be extended by other components to implement their specific data-fetching logic.

Creating the BasePaginationComponent
Let's define a generic BasePaginationComponent class that handles the core pagination functionality:​

import { finalize } from 'rxjs/operators';
import { Observable } from 'rxjs';

export abstract class BasePaginationComponent<T> {
  data: T[] = [];
  currentPage = 1;
  pageSize = 10;
  totalItems = 0;
  isLoading = false;

  // Abstract method to fetch data; must be implemented by subclasses
  protected abstract fetchData(page: number, size: number): Observable<{ items: T[]; total: number }>;

  // Method to load data for a specific page
  loadPage(page: number): void {
    this.isLoading = true;
    this.fetchData(page, this.pageSize)
      .pipe(finalize(() => (this.isLoading = false)))
      .subscribe(response => {
        this.data = response.items;
        this.totalItems = response.total;
        this.currentPage = page;
      });
  }

  // Method to handle page change events (e.g., from a paginator component)
  onPageChange(page: number): void {
    this.loadPage(page);
  }
}
Enter fullscreen mode Exit fullscreen mode

In this base component:​

  • data: Holds the current page's data items.​
  • currentPage: Tracks the current page number.​
  • pageSize: Defines the number of items per page.​
  • totalItems: Stores the total number of items across all pages.​
  • isLoading: Indicates whether a data fetch operation is in progress.​
  • fetchData: An abstract method that must be implemented by subclasses to fetch data for a given page and page size.​
  • loadPage: Handles the logic for loading data for a specific page.​
  • onPageChange: A helper method to be called when the page changes, which in turn calls loadPage.​

Implementing the PaginationControlsComponent

import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-pagination-controls',
  templateUrl: './pagination-controls.component.html'
})
export class PaginationControlsComponent implements OnChanges {
  @Input() currentPage: number = 1;
  @Input() totalItems: number = 0;
  @Input() pageSize: number = 10;
  @Output() pageChange: EventEmitter<number> = new EventEmitter<number>();

  totalPages: number = 0;
  pages: number[] = [];

  ngOnChanges(changes: SimpleChanges): void {
    this.totalPages = Math.ceil(this.totalItems / this.pageSize);
    this.pages = Array.from({ length: this.totalPages }, (_, i) => i + 1);
  }

  changePage(page: number): void {
    if (page >= 1 && page <= this.totalPages && page !== this.currentPage) {
      this.pageChange.emit(page);
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

Extending the Base Component
Now, let's create a component that extends BasePaginationComponent to display a list of users:​

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { BasePaginationComponent } from './base-pagination.component';
import { UserService } from './user.service';
import { User } from './user.model';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html'
})
export class UserListComponent extends BasePaginationComponent<User> implements OnInit {
  constructor(private userService: UserService) {
    super();
  }

  ngOnInit(): void {
    this.loadPage(this.currentPage);
  }

  protected fetchData(page: number, size: number): Observable<{ items: User[]; total: number }> {
    return this.userService.getUsers(page, size);
  }
}


Enter fullscreen mode Exit fullscreen mode

In this example:​

  • UserListComponent extends BasePaginationComponent, specifying User as the data type.​
  • The fetchData method is implemented to fetch users from the UserService.​
  • The template displays the list of users and includes a custom app-pagination-controls component to handle pagination UI.

Handling Edge Cases in Your Pagination System

While the basic implementation works well, a production-ready pagination system should address these common edge cases:

  • Large datasets: Display a limited range of pages (first, last, and a few around current) with ellipses instead of showing all page numbers
  • Navigation boundaries: Disable Previous/Next buttons at first/last pages, and hide pagination entirely for single-page results
  • Empty results: Provide user feedback when no items exist instead of showing empty pagination controls
  • Page size changes: Reset to first page when page size changes to ensure consistent user experience
  • URL integration: Consider syncing pagination state with URL parameters for bookmarking and sharing specific pages
  • Responsive design: Adapt controls for smaller screens with simplified mobile-friendly navigation
  • Accessibility: Implement keyboard navigation and proper ARIA labels for inclusive user experience
  • Error handling: Gracefully manage network failures during server-side pagination operations

AWS GenAI LIVE image

Real challenges. Real solutions. Real talk.

From technical discussions to philosophical debates, AWS and AWS Partners examine the impact and evolution of gen AI.

Learn more

Top comments (0)

Redis image

Short-term memory for faster
AI agents

AI agents struggle with latency and context switching. Redis fixes it with a fast, in-memory layer for short-term context—plus native support for vectors and semi-structured data to keep real-time workflows on track.

Start building

👋 Kindness is contagious

Explore this insightful write-up, celebrated by our thriving DEV Community. Developers everywhere are invited to contribute and elevate our shared expertise.

A simple "thank you" can brighten someone’s day—leave your appreciation in the comments!

On DEV, knowledge-sharing fuels our progress and strengthens our community ties. Found this useful? A quick thank you to the author makes all the difference.

Okay