Cannot add pagination to my angular project

Viewed 659

I have tried all the things I know but with no success. I want to add pagination to a covid-19 table. The paginator shows how much rows are in the table but it is not splitting them into pages.

Here is the code I've done.

Here is my component.ts:

import { AfterViewInit, Component, HostListener, OnInit, ViewChild, } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTable } from '@angular/material/table';
import { merge } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Countries, sortData } from '../app.model';
import { AppService } from '../app.service';
import { DataTableDataSource } from './data-table-datasource';
import { animate, state, style, transition, trigger, } from '@angular/animations';

@Component({
    selector: 'app-data-table',
    templateUrl: './data-table.component.html',
    styleUrls: ['./data-table.component.css'],
    animations: [
        trigger('detailExpand', [
            state('collapsed', style({ height: '0px', minHeight: '0' })),
            state('expanded', style({ height: '*' })),
            transition(
                'expanded <=> collapsed',
                animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')
            ),
        ]),
    ],
})
export class DataTableComponent implements AfterViewInit {
    @ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
    @ViewChild(MatSort) sort!: MatSort;
    @ViewChild(MatTable) table!: MatTable<Countries>;
    @ViewChild('paginator', { read: MatPaginator }) paginatorlist: MatPaginator;
    dataSource: DataTableDataSource;
    filter: sortData = new sortData();

    /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
    displayedColumns = [];
    scrWidth: any;
    constructor(public appService: AppService) { }

    ngOnInit() {
        this.dataSource = new DataTableDataSource(this.appService);
        this.sortData(false);
        this.setupTable();
        this.getScreenSize();
    }

    sortData(isChange: boolean) {
        if (isChange) {
            this.filter.pageSize = this.paginator.pageSize;
            this.filter.pageNumber = this.paginator.pageIndex + 1;
            // this.filter.sortColumn = this.sort.active;
            // this.filter.sortDirection = this.sort.direction;
            console.log(this.paginator.pageIndex);
        } else {
            this.filter.pageSize = 15;
            this.filter.pageNumber = 1;
            // this.filter.sortColumn = 'todayCases';
            // this.filter.sortDirection = 'true';
            // console.log(this.filter);
        }
        this.dataSource.loadData(this.filter);
        (error) => console.error(error);
    }

    ngAfterViewInit() {
        this.dataSource.paginator = this.paginator;
        this.dataSource.sort = this.sort;
    }
}

Here is my datasource.ts:

import { DataSource } from '@angular/cdk/collections';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { finalize, map } from 'rxjs/operators';
import { Observable, of as observableOf, merge, BehaviorSubject } from 'rxjs';
import { ApiResponse, Countries, sortData } from '../app.model';
import { AppService } from '../app.service';

export class DataTableDataSource extends DataSource<Countries> {
    
    public dataSubject = new BehaviorSubject<Countries[]>([]);
    private loadingSubject = new BehaviorSubject<boolean>(false);
    public loading$ = this.loadingSubject.asObservable();
    private totalDataCountSubject = new BehaviorSubject<number>(0);
    public totalDataCount$ = this.totalDataCountSubject.asObservable();
    paginator: MatPaginator;
    sort: MatSort;

    constructor(private appService: AppService) {
        super();
    }

    loadData(filter: sortData) {
        this.appService
            .getData(filter)
            .pipe(finalize(() => this.loadingSubject.next(false)))
            .subscribe((data: any) => {
                // console.log(data.response);
                if (data) {
                    this.totalDataCountSubject.next(data.length);
                    this.dataSubject.next(data);
                    console.log(data.length);
                }
            });
    }

    connect(): Observable<Countries[]> {
        return this.dataSubject.asObservable();
    }

    disconnect(): void {
        this.dataSubject.complete();
        this.totalDataCountSubject.complete();
        this.loadingSubject.complete();
    }
}

And here is my HTML:

<div class="mat-elevation-z8 data-table">
    <table mat-table multiTemplateDataRows class="full-width-table" matSort matSortActive="todayCases" matSortDirection="asc"
        aria-label="Elements" [dataSource]="dataSource">
        <!-- Country Column -->
        <ng-container matColumnDef="country">
            <th mat-header-cell *matHeaderCellDef mat-sort-header>Country</th>
            <td mat-cell *matCellDef="let row">{{ row.country }}</td>
        </ng-container>

        <!-- Name Column -->
        <ng-container matColumnDef="cases">
            <th mat-header-cell *matHeaderCellDef mat-sort-header>Cases</th>
            <td mat-cell *matCellDef="let row">{{ row.cases }}</td>
        </ng-container>
        <!-- Id Column -->
        <ng-container matColumnDef="active">
            <th mat-header-cell *matHeaderCellDef mat-sort-header>Active</th>
            <td mat-cell *matCellDef="let row">{{ row.active }}</td>
        </ng-container>
        <!-- Id Column -->
        <ng-container matColumnDef="todayCases">
            <th mat-header-cell *matHeaderCellDef mat-sort-header>Today Cases</th>
            <td mat-cell *matCellDef="let row">{{ row.todayCases }}</td>
        </ng-container>
        <!-- Id Column -->
        <ng-container matColumnDef="todayDeaths">
            <th mat-header-cell *matHeaderCellDef mat-sort-header>Total Deaths</th>
            <td mat-cell *matCellDef="let row">{{ row.todayDeaths }}</td>
        </ng-container>

        <!-- Id Column -->
        <ng-container class="recovered" matColumnDef="recovered">
            <th mat-header-cell *matHeaderCellDef mat-sort-header>Recovered</th>
            <td mat-cell *matCellDef="let row">{{ row.recovered }}</td>
        </ng-container>
        <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
        <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
    </table>

    <mat-paginator #paginator [length]="dataSource.totalDataCount$ | async" [pageSize]="15" [pageSizeOptions]="[15, 20, 25]">
    </mat-paginator>
</div>

Thanks in advance

2 Answers

The issue is you have to set the total available data length to the pageSize parameter of the paginator.

For example, you have 30 countries' data and your pageSize is 10. So, you have to set the pageSize parameter of the paginator to 30 and your visible data will be 10.

I've mocked your AppService with static data. And showed how to populate data for pagination.

And you also missed filtering data after change of pageSize or page. I've also done that.

Observe the demo and you'll be able to understand.

Working solution created at Stackblitz

It seems your Api end doesn't support data paging. It is returns all data even if you request with pageSize and pageNumber parameters in query string.

The solution is to page the data on client side. The data source type you are using currently (DataSource<T>) will add more complexity in client side data paging scenario. You should use the MatTableDataSource<T> as the data-source - it has automatic data paging support once you feed it all the data.

Following is a modified/simplified version of your StackBlitz that you can try -

Component code -

export class GalleryComponent implements OnInit {

    @ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
    @ViewChild(MatSort) sort!: MatSort;
    dataSource = new MatTableDataSource<Countries[]>([]);
    displayedColumns = ['country', 'cases', 'active', 'todayDeaths', 'recovered'];
    filter: sortData = new sortData();

    constructor(public appService: AppService) { }

    ngOnInit() {
        this.loadData();
    }

    loadData() {
        this.filter.sortColumn = 'country';
        this.filter.sortDirection = 'asc';
        this.appService.getData(this.filter).subscribe(
            p => {
                this.dataSource.data = p;
            },
            e => {
                console.log(e);
            });
    }

    ngAfterViewInit() {
        this.dataSource.paginator = this.paginator;
        this.dataSource.sort = this.sort;
    }
}

In the Template code only remove the length binding from the mat-paginator -

<mat-paginator [pageSize]="15" [pageSizeOptions]="[15, 20, 25]"> </mat-paginator>

This should be enough to make your pagination work. You don't need to use the DataTableDataSource.

Related