Angular Material Pagination – How to replace first and last icons with text?

Issue

I have pagination as shown below;

enter image description here

How can I replace the first and last icons with actual text to get something similar to the below. I managed to reproduce and style all other items other than replacing the first and last icons.

enter image description here

HTML

<mat-paginator style-paginator showFirstLastButtons [showTotalPages]="3" [refreshButtons]="dataSource.data.length" [length]="dataSource.data.length" [pageSizeOptions]="[15, 30, 45, 60]">
</mat-paginator>

style-page-directive.ts (Imported in module)

  import {
  ElementRef,
  AfterViewInit,
  Directive,
  Host,
  Optional,
  Renderer2,
  Self,
  ViewContainerRef,
  Input,
} from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatButton } from '@angular/material/button';

interface PageObject {
  length: number;
  pageIndex: number;
  pageSize: number;
  previousPageIndex: number;
}

@Directive({
  selector: '[style-paginator]',
})
export class StylePaginatorDirective {
  private _pageGapTxt = '...';
  private _rangeStart: number;
  private _rangeEnd: number;
  private _buttons = [];
  private _curPageObj: PageObject = {
    length: 0,
    pageIndex: 0,
    pageSize: 0,
    previousPageIndex: 0,
  };

  @Input()
  get showTotalPages(): number {
    return this._showTotalPages;
  }
  set showTotalPages(value: number) {
    this._showTotalPages = value % 2 == 0 ? value + 1 : value;
  }
  private _showTotalPages = 2;

  get inc(): number {
    return this._showTotalPages % 2 == 0
      ? this.showTotalPages / 2
      : (this.showTotalPages - 1) / 2;
  }

  get numOfPages(): number {
    return this.matPag.getNumberOfPages();
  }

  get lastPageIndex(): number {
    return this.matPag.getNumberOfPages() - 1;
  }

  @Input()
  set refreshButtons(numRows: number) {
    if (numRows > 0) {
      this.buildPageNumbers();
      this.matPag.firstPage();
    }
  }

  constructor(
    @Host() @Self() @Optional() private readonly matPag: MatPaginator,
    private vr: ViewContainerRef,
    private ren: Renderer2
  ) {
    //to rerender buttons on items per page change and first, last, next and prior buttons
    this.matPag.page.subscribe((e: PageObject) => {
      if (
        this._curPageObj.pageSize != e.pageSize &&
        this._curPageObj.pageIndex != 0
      ) {
        e.pageIndex = 0;
        this._rangeStart = 0;
        this._rangeEnd = this._showTotalPages - 1;
      }
      this._curPageObj = e;

      this.initPageRange();
    });
  }

  private buildPageNumbers() {
    let roleSet = localStorage.getItem('role');

    const actionContainer = this.vr.element.nativeElement.querySelector(
      'div.mat-paginator-range-actions'
    );
    const nextPageNode = this.vr.element.nativeElement.querySelector(
      'button.mat-paginator-navigation-next'
    );
    const prevButtonCount = this._buttons.length;

    // remove buttons before creating new ones
    if (this._buttons.length > 0) {
      this._buttons.forEach((button) => {
        this.ren.removeChild(actionContainer, button);
      });
      //Empty state array
      this._buttons.length = 0;
    }

    //initialize next page and last page buttons
    if (this._buttons.length == 0) {
      let nodeArray =
        this.vr.element.nativeElement.childNodes[0].childNodes[0].childNodes[2]
          .childNodes;
      setTimeout(() => {
        for (let i = 0; i < nodeArray.length; i++) {
          if (nodeArray[i].nodeName === 'BUTTON') {
            if (nodeArray[i].innerHTML.length > 100 && nodeArray[i].disabled) {
              if (roleSet == 'crew') {
                 this.ren.setStyle(
                  nodeArray[i],
                  'background-color',
                  'rgba(250, 250, 250, 1)'
                );
              } else if (roleSet == 'shipping' || roleSet == 'isat') {
                this.ren.setStyle(
                  nodeArray[i],
                  'background-color',
                  'rgba(255, 255, 255, 255)'
                );
              } else {
                this.ren.setStyle(
                  nodeArray[i],
                  'background-color',
                  'rgba(255, 255, 255, 255)'
                );
              }
              this.ren.setStyle(nodeArray[i], 'color', 'white');
              this.ren.setStyle(nodeArray[i], 'color', 'black');
              this.ren.setStyle(nodeArray[i], 'margin', '.5%');
            } else if (
              nodeArray[i].innerHTML.length > 100 &&
              !nodeArray[i].disabled
            ) {
              if (roleSet == 'crew') {
                this.ren.setStyle(
                  nodeArray[i],
                  'background-color',
                  'rgba(250, 250, 250, 1)'
                );
              } else if (roleSet == 'shipping' || roleSet == 'isat') {
                this.ren.setStyle(
                  nodeArray[i],
                  'background-color',
                  'rgba(255, 255, 255, 255)'
                );
              } else {
                this.ren.setStyle(
                  nodeArray[i],
                  'background-color',
                  'rgba(255, 255, 255, 255)'
                );
              }
              this.ren.setStyle(nodeArray[i], 'color', 'white');
              this.ren.setStyle(nodeArray[i], 'color', 'black');
              this.ren.setStyle(nodeArray[i], 'margin', '.5%');
              if (roleSet == 'crew') {
                this.ren.setStyle(nodeArray[i], 'background-color', '#fafafa');
              } else if (roleSet == 'shipping' || roleSet == 'isat') {
                this.ren.setStyle(nodeArray[i], 'background-color', '#fff');
              } else {
                this.ren.setStyle(nodeArray[i], 'background-color', '#fff');
              }
              this.ren.setStyle(nodeArray[i], 'box-shadow', 'none');
            } else if (nodeArray[i].disabled) {
              if (roleSet == 'crew') {
                this.ren.setStyle(nodeArray[i], 'background-color', '#fafafa');
              } else if (roleSet == 'shipping' || roleSet == 'isat') {
                this.ren.setStyle(nodeArray[i], 'background-color', '#fafafa');
              } else {
                this.ren.setStyle(nodeArray[i], 'background-color', '#fff');
              }
              this.ren.setStyle(nodeArray[i], 'box-shadow', 'none');
            }
          }
        }
      });
    }

    for (let i = 0; i < this.numOfPages; i++) {
      if (i >= this._rangeStart && i <= this._rangeEnd) {
        this.ren.insertBefore(
          actionContainer,
          this.createButton(i, this.matPag.pageIndex),
          nextPageNode
        );
      }

      if (i == this._rangeEnd) {
        this.ren.insertBefore(
          actionContainer,
          this.createButton(this._pageGapTxt, this._rangeEnd),
          nextPageNode
        );
      }
    }
  }

  private createButton(i: any, pageIndex: number): any {
    let roleSet = localStorage.getItem('role');

    const linkBtn: MatButton = this.ren.createElement('button');
    this.ren.addClass(linkBtn, 'mat-mini-fab');
    this.ren.setStyle(linkBtn, 'margin', '1%');
    if (roleSet == 'crew') {
      this.ren.setStyle(linkBtn, 'background-color', '#fafafa');
    } else if (roleSet == 'shipping' || roleSet == 'isat') {
      this.ren.setStyle(linkBtn, 'background-color', '#fff');
    } else {
      this.ren.setStyle(linkBtn, 'background-color', '#fff');
    }
    this.ren.setStyle(linkBtn, 'box-shadow', 'none');

    const pagingTxt = isNaN(i) ? this._pageGapTxt : +(i + 1);
    const text = this.ren.createText(pagingTxt + '');

    this.ren.addClass(linkBtn, 'mat-custom-page');
    switch (i) {
      case pageIndex:
        this.ren.setAttribute(linkBtn, 'disabled', 'disabled');
        break;
      case this._pageGapTxt:
        let newIndex = this._curPageObj.pageIndex + this._showTotalPages;

        if (newIndex >= this.numOfPages) newIndex = this.lastPageIndex;

        if (pageIndex != this.lastPageIndex) {
          this.ren.listen(linkBtn, 'click', () => {
            console.log('working: ', pageIndex);
            this.switchPage(newIndex);
          });
        }

        if (pageIndex == this.lastPageIndex) {
          this.ren.setAttribute(linkBtn, 'disabled', 'disabled');
        }
        break;
      default:
        this.ren.listen(linkBtn, 'click', () => {
          this.switchPage(i);
        });
        break;
    }

    this.ren.appendChild(linkBtn, text);
    //Add button to private array for state
    this._buttons.push(linkBtn);
    return linkBtn;
  }
  //calculates the button range based on class input parameters and based on current page index value. Used to render new buttons after event.
  private initPageRange(): void {
    const middleIndex = (this._rangeStart + this._rangeEnd) / 2;

    this._rangeStart = this.calcRangeStart(middleIndex);
    this._rangeEnd = this.calcRangeEnd(middleIndex);
    this.buildPageNumbers();
  }

  //Helper function To calculate start of button range
  private calcRangeStart(middleIndex: number): number {
    switch (true) {
      case this._curPageObj.pageIndex == 0 && this._rangeStart != 0:
        return 0;
      case this._curPageObj.pageIndex > this._rangeEnd:
        return this._curPageObj.pageIndex + this.inc > this.lastPageIndex
          ? this.lastPageIndex - this.inc * 2
          : this._curPageObj.pageIndex - this.inc;
      case this._curPageObj.pageIndex > this._curPageObj.previousPageIndex &&
        this._curPageObj.pageIndex > middleIndex &&
        this._rangeEnd < this.lastPageIndex:
        return this._rangeStart + 1;
      case this._curPageObj.pageIndex < this._curPageObj.previousPageIndex &&
        this._curPageObj.pageIndex < middleIndex &&
        this._rangeStart > 0:
        return this._rangeStart - 1;
      default:
        return this._rangeStart;
    }
  }
  //Helpter function to calculate end of button range
  private calcRangeEnd(middleIndex: number): number {
    switch (true) {
      case this._curPageObj.pageIndex == 0 &&
        this._rangeEnd != this._showTotalPages:
        return this._showTotalPages - 1;
      case this._curPageObj.pageIndex > this._rangeEnd:
        return this._curPageObj.pageIndex + this.inc > this.lastPageIndex
          ? this.lastPageIndex
          : this._curPageObj.pageIndex + 1;
      case this._curPageObj.pageIndex > this._curPageObj.previousPageIndex &&
        this._curPageObj.pageIndex > middleIndex &&
        this._rangeEnd < this.lastPageIndex:
        return this._rangeEnd + 1;
      case this._curPageObj.pageIndex < this._curPageObj.previousPageIndex &&
        this._curPageObj.pageIndex < middleIndex &&
        this._rangeStart >= 0 &&
        this._rangeEnd > this._showTotalPages - 1:
        return this._rangeEnd - 1;
      default:
        return this._rangeEnd;
    }
  }
  //Helper function to switch page on non first, last, next and previous buttons only.
  private switchPage(i: number): void {
    const previousPageIndex = this.matPag.pageIndex;
    this.matPag.pageIndex = i;
    this.matPag['_emitPageEvent'](previousPageIndex);
    this.initPageRange();
  }
  //Initialize default state after view init
  public ngAfterViewInit() {
    this._rangeStart = 0;
    this._rangeEnd = this._showTotalPages - 1;
    this.initPageRange();
  }
}

CSS

I tried something like below but to no avail.

::ng-deep .mat-paginator-navigation-first .mat-paginator-icon {
    display: none;
}

::ng-deep .mat-paginator-navigation-first {
    content: "TEST" !important;
}

Solution

You can try to set the text programatically:

    const lastBtn = this.el.nativeElement.querySelector(
      '.mat-paginator-navigation-last'
    );
    if (lastBtn) {
      lastBtn.innerHTML = 'Last';
    }
    const firstBtn = this.el.nativeElement.querySelector(
      '.mat-paginator-navigation-first'
    );
    if (firstBtn) {
      firstBtn.innerHTML = 'First';
    }

I have created a stackblitz: https://stackblitz.com/edit/angular-dy9j4m?file=src/app/table-pagination-example.ts

Answered By – shhdharmen

Answer Checked By – Candace Johnson (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.