Search code examples
node.jsangularexpressngrxmat-pagination

Angular server side pagination , ngrx and nodejs with express, I get discrepancy indexes between stacks


I implemented the server side pagination in angular and it is working fine, the problem is the page I set initially on 1 , but the mat-paginator starts at index 2. When I change pages it works fine, I`d like to know how to solve this bug.

This is the paginate method in nodejs:

paginate() {
    const page = this.queryString.page * 1 || 1
    const limit = this.queryString.limit * 1 || 100
    const skip = (page - 1) * limit

    this.query = this.query.skip(skip).limit(limit)

    return this
  }

This is the component:

import { Component } from '@angular/core'
import { Equipment } from '../../models/equipment.model'
import { DialogService } from 'src/app/shared/services/dialog.service'
import { Store } from '@ngrx/store'
import {
    getEquipments,
    getEquipmentsCount,
    getIsLoading,
    State
} from '../../state'
import { Observable } from 'rxjs'
import { EquipmentPageActions } from '../../state/actions'
import { PageEvent } from '@angular/material/paginator'

@Component({
    selector: 'app-equipments-list',
    templateUrl: './equipments-list.component.html',
    styleUrls: ['./equipments-list.component.css']
})
export class EquipmentsListComponent {
    isLoading$: Observable<boolean>
    equipments$: Observable<Equipment[]>
    count$: Observable<number>
    page = 1
    pageSize = 3

    constructor(
        private dialogService: DialogService,
        private store: Store<State>
    ) {}

    ngOnInit(): void {
        this.store.dispatch(
            EquipmentPageActions.loadEquipments({
                page: this.page,
                limit: this.pageSize
            })
        )
        this.equipments$ = this.store.select(getEquipments)
        this.isLoading$ = this.store.select(getIsLoading)
        this.count$ = this.store.select(getEquipmentsCount)
    }

    onDeleteEquipment(id: string): void {
        this.dialogService
            .confirmDialog({
                title: 'DELETE EQUIPMENT',
                message: 'Are you sure you want to delete?',
                confirmText: 'No',
                cancelText: 'Yes'
            })
            .subscribe((confirm) => {
                if (confirm) {
                    this.store.dispatch(
                        EquipmentPageActions.deleteEquipment({ id })
                    )
                }
            })
    }

    handlePageChange(pageEvent: PageEvent): void {
        this.page = pageEvent.pageIndex
        this.pageSize = pageEvent.pageSize
        this.store.dispatch(
            EquipmentPageActions.loadEquipments({
                page: this.page + 1,
                limit: this.pageSize
            })
        )
    }
}

This is the template:

<div
    class="d-flex flex-column align-items-center justify-content-center h-100"
    *ngIf="isLoading$ | async"
>
    <div class="spinner-border" role="status">
        <span>Loading...</span>
    </div>
</div>

<div class="container" *ngIf="!(isLoading$ | async)">
    <div class="row">
        <div class="col-xs-12">
            <h1 class="fw-bold text-center">Equipments</h1>
            <div class="mb-2">
                <a
                    class="btn btn-primary btn-sm"
                    type="button"
                    [routerLink]="'add'"
                    >Add</a
                >
            </div>
            <table class="table shadow-lg table-responsive">
                <thead>
                    <tr>
                        <th scope="col">Name</th>
                        <th scope="col">Power</th>
                        <th scope="col">Installation</th>
                        <th scope="col">Actions</th>
                    </tr>
                </thead>
                <tbody *ngIf="equipments$ | async as equipments">
                    <tr scope="row" *ngFor="let equip of equipments">
                        <td>{{ equip.name }}</td>
                        <td>{{ equip.powerRequirement }}</td>
                        <td class="text-nowrap">
                            {{ equip.installationDate | date : 'dd-MM-yyyy' }}
                        </td>
                        <td class="text-nowrap">
                            <button
                                class="btn btn-secondary btn-sm me-1"
                                [routerLink]="[equip._id]"
                            >
                                View
                            </button>
                            <a
                                class="btn btn-primary btn-sm me-1"
                                type="button"
                                [routerLink]="[equip._id + '/edit']"
                                >Edit</a
                            >
                            <button
                                class="btn btn-danger btn-sm"
                                type="button"
                                (click)="onDeleteEquipment(equip._id)"
                            >
                                Delete
                            </button>
                        </td>
                    </tr>
                </tbody>
            </table>
            <div class="text-center" *ngIf="equipments$ | async as equipments">
                <p *ngIf="equipments.length === 0">No equipments</p>
            </div>
            <mat-paginator
                [length]="count$ | async"
                [pageSize]="pageSize"
                [pageSizeOptions]="[3, 6, 9]"
                [showFirstLastButtons]="true"
                [pageIndex]="page"
                (page)="handlePageChange($event)"
            >
            </mat-paginator>
        </div>
    </div>
</div>

Any idea where I am missing the problem? This is the paginator starting at page 2

initially the handlePageChange was :

handlePageChange(pageEvent: PageEvent): void {
        this.page = pageEvent.pageIndex
        this.pageSize = pageEvent.pageSize
        this.store.dispatch(
            EquipmentPageActions.loadEquipments({
                page: this.page,
                limit: this.pageSize
            })
        )
    }

And the page would start at 1 but resulted for the server as 0 so everything was loaded. Then I added + 1 to the this.pageSize = pageEvent.pageSize , so I got the page 2 loaded, but when I go on page 1 works fine.


Solution

  • I`ve managed to sort the problem adding + 1 to the page in the ngOnInit

    ngOnInit(): void {
            this.store.dispatch(
                EquipmentPageActions.loadEquipments({
                    page: this.page + 1,
                    limit: this.pageSize
                })
            )
            this.equipments$ = this.store.select(getEquipments)
            this.isLoading$ = this.store.select(getIsLoading)
            this.count$ = this.store.select(getEquipmentsCount)
        }