最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

angular - Ngx-Pagination not navigating and got disabled - Stack Overflow

programmeradmin1浏览0评论

Using Angular, I am consuming an API from ASP.NET Core Web API, I have this endpoint that performs Query Filter, ngx-Pagination, ngx-datepicker, and Sorting. It can perform

  1. Search Query,
  2. Search Query and Export to Excel

GET Method (example of the same endpoint):

https://localhost:7244/api/v1/transactions/all-transactions-by-created_date?StartDate=2022-01-01&EndDate=2023-01-01&exportToExcel=false

https://localhost:7244/api/v1/transactions/all-transactions-by-created_date?exportToExcel=false

https://localhost:7244/api/v1/transactions/all-transactions-by-created_date?SortBy=Ascending&IsSortAscending=true&exportToExcel=false

https://localhost:7244/api/v1/transactions/all-transactions-by-created_date?SearchQuery=428ce81ffa8742f2b6779ba504eb4d80&StartDate=2022-02-02&EndDate=2025-01-01&exportToExcel=false

JSON Response:

{
  "data": {
    "pageItems": [
      {
        "id": 1073025,
        "aggregatorId": "fdfdfdfdassa",
        "acctNo": "5432156",
        "amount": 3000,
        "acctType": null,
        "source": "LEMMY",
        "sourcePtid": 1.24110443187,
        "transactionType": "C",
        "description": "DSDSDSDSDSDS",
        "createDt": "2024-11-12T16:46:16.877",
        "effectiveDt": "2024-11-12T16:46:16.877",
        "tfrAcctNo": null,
        "currency": null,
        "tranCode": null,
        "originTracerNo": "DSDSDSDSDSDS",
        "channelId": 0,
        "transactionId": "SDSDSDSDSDS",
        "payerAcctNo": null,
        "payerName": "DSDSDS HGHGHG",
        "originBankCode": "000001",
        "notified": "N",
        "notifiedDt": "2025-01-24T11:26:20.55",
        "channelName": "NIP",
        "charges": 0,
        "instantSettlementCount": 0,
        "instantSettledStatus": null,
        "instantSettledMessage": null,
        "instantSettlementPostingDate": "2025-01-24T11:26:20.55",
        "isRequeried": false,
        "instantSettlementProcessingCount": 0,
        "instantSettlementProcessingNextRuntime": "0001-01-01T00:00:00",
        "stagingDt": "2025-01-24T11:26:20.55",
        "insertDtTm": "2024-11-12T16:46:16.95",
        "errorText": null,
        "errorDt": "0001-01-01T00:00:00",
        "notifyCount": 0,
        "fcubeReference": "M243170707406487",
        "cbaReference": "MAS243170707409465",
        "xapiBatchId": "117853997723599116619010321868",
        "settlementAccountNumber": null
      },
       ...
    ],
    "currentPage": 1,
    "pageSize": 10,
    "numberOfPages": 1385,
    "totalRecord": 13849,
    "previousPage": 0
  },
  "successful": true,
  "message": "All Transactions Retrieved Successfully",
  "statusCode": 200,
  "responseCode": null,
  "responseDescription": null
}

I am consuming the Asp.NET Core Web API using Angular: with ngx-pagination, ngx-datepicker, page filter (10, 50, 100) ItemPerPage, Download to Excel, StartDate and EndDate must be separate.

  1. And should not allow future Date, and user cannot backdate beyond one year
  2. By default no data should be loaded on the Page until when the Search Button is Submitted
  3. The Search and ExportToExcel buttons will not be enabled until when user select StartDate and EndDate (they are both required).

You can see it in the screenshot.

MAIN CODE:

export interface IPageChangedEvent {
    itemsPerPage: number;
    page: number;
  }

admin-transaction-created-date-list.model.ts:

export interface IAdminTransactionCreatedDateList {
    aggregatorId: string;
    acctNo: string;
    amount: number;
    acctType: string | null;
    source: string;
    sourcePtid: number;
    transactionType: string;
    description: string;
    createDt: string;
    effectiveDt: string;
    tfrAcctNo: string | null;
    currency: string | null;
    tranCode: string | null;
    originTracerNo: string;
    channelId: number;
    transactionId: string;
    payerAcctNo: string | null;
    payerName: string;
    originBankCode: string;
    notified: string;
    notifiedDt: string;
    channelName: string;
    charges: number;
    thirdPartyComm: number;
    instantSettlementCount: number;
    instantSettledStatus: string;
    instantSettledMessage: string;
    instantSettlementPostingDate: string;
    isRequeried: boolean;
    instantSettlementProcessingCount: number;
    instantSettlementProcessingNextRuntime: string;
    stagingDt: string;
    insertDtTm: string;
    errorText: string;
    errorDt: string;
    notifyCount: number;
    fcubeReference: string;
    cbaReference: string;
    xapiBatchId: string;
    settlementAccountNumber: string | null;  
  }

  export interface PageResult<T> {
    pageItems: T[];
    currentPage: number;
    pageSize: number;
    numberOfPages: number;
    totalRecord: number;
    previousPage: number;
  }
  
  export interface IAdminTransactionCreatedDateResponse<T> {
    data: PageResult<T>;
    successful: boolean;
    message: string;
    statusCode: number;
    responseCode: string | null;
    responseDescription: string | null;
  }  
  
  export interface IAdminTransactionCreatedDateFilter {
    pageNumber: number;
    pageSize: number;
    searchQuery?: string;
    startDate: Date | null;
    endDate: Date | null;
    sortBy?: string;
    isSortAscending?: boolean;
  }

admin-transaction-service.ts:

import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
import { Observable } from 'rxjs';
import {
  HttpClient,
  HttpHeaders,
  HttpParams,
  HttpResponse,
} from '@angular/common/http';
import { 
  IAdminTransactionCreatedDateList,
  IAdminTransactionCreatedDateResponse, 
  IAdminTransactionCreatedDateFilter 
} from '../../../features/admin/models/transaction/admin-transaction-created-date-list.model';

@Injectable({
  providedIn: 'root'
})
export class AdminTransactionService {
  baseUrl = environment.baseHost;
  token = localStorage.getItem('token');

  constructor(private http: HttpClient) {}

  getAllTransactions(filter: IAdminTransactionCreatedDateFilter): Observable<IAdminTransactionCreatedDateResponse<IAdminTransactionCreatedDateList>> {
    let params = new HttpParams()
      .set('PageNumber', filter.pageNumber.toString())
      .set('PageSize', filter.pageSize.toString())
      .set('exportToExcel', 'false');

    if (filter.searchQuery) {
      params = params.set('SearchQuery', filter.searchQuery);
    }
    if (filter.sortBy) {
      params = params.set('SortBy', filter.sortBy);
      params = params.set('IsSortAscending', filter.isSortAscending?.toString() || 'false');
    }
    if (filter.startDate) {
      params = params.set('StartDate', filter.startDate.toISOString());
    }
    if (filter.endDate) {
      params = params.set('EndDate', filter.endDate.toISOString());
    }

    return this.http.get<IAdminTransactionCreatedDateResponse<IAdminTransactionCreatedDateList>>(`${this.baseUrl}/transactions/all-transactions-by-created_date`, { params });
  }  

  exportToExcel(filter: IAdminTransactionCreatedDateFilter): Observable<Blob> {
    let params = new HttpParams()
      .set('PageNumber', filter.pageNumber.toString())
      .set('PageSize', filter.pageSize.toString())
      .set('exportToExcel', 'true');

    if (filter.searchQuery) {
      params = params.set('SearchQuery', filter.searchQuery);
    }
    if (filter.startDate) {
      params = params.set('StartDate', filter.startDate.toISOString());
    }
    if (filter.endDate) {
      params = params.set('EndDate', filter.endDate.toISOString());
    }

    return this.http.get(`${this.baseUrl}/transactions/all-transactions-by-created_date`,
      { params, responseType: 'blob' }
    );
  }  
}

admin-created-date-transactions.ts:

import { Component, OnInit } from '@angular/core';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { AdminTransactionService } from 'src/app/features/admin/services/admin-transaction.service';
import {   
  IAdminTransactionCreatedDateList, 
  IAdminTransactionCreatedDateFilter  
} from 'src/app/features/admin/models/transaction/admin-transaction-created-date-list.model';
import { BsDatepickerConfig } from 'ngx-bootstrap/datepicker';
import { saveAs } from 'file-saver';
import * as moment from 'moment';
import { IPageChangedEvent } from 'src/app/shared/models/page-changed-event.model';

@Component({
  selector: 'app-admin-created-date-transactions',
  templateUrl: './admin-created-date-transactionsponent.html',
  styleUrls: ['./admin-created-date-transactionsponent.scss']
})
export class AdminCreatedDateTransactionsComponent implements OnInit {
  bsModalRef?: BsModalRef;
  pageTitle = 'Transaction By Created Date';
  pageLabel = 'Transaction By Created Date List';
  advanceSearch = 'Advance Search';
  search = 'Search';  
  searchForm!: FormGroup;
  transactions: IAdminTransactionCreatedDateList[] = [];
  currentPage = 1;
  pageSize = 10;
  totalRecords = 0;
  hasSearchResults = false;
  maxDate = new Date();
  minDate = new Date();
  loading = false;
  pageSizeOptions = [10, 50, 100];
  bsConfig!: Partial<BsDatepickerConfig>;
  Math = Math;

  constructor(
    private fb: FormBuilder,
    private transactionService: AdminTransactionService,
    private toastr: ToastrService
  ) {
    this.minDate.setFullYear(this.minDate.getFullYear() - 1);
    
    this.bsConfig = {
      dateInputFormat: 'DD-MMM-YYYY',
      isAnimated: true,
      returnFocusToInput: true,
      showClearButton: true,
      showWeekNumbers: false,
      adaptivePosition: true,
      containerClass: 'theme-dark-blue'
    };

    this.initializeForm();
  }

  ngOnInit(): void {
    this.setupFormValidation();
  }

  private initializeForm(): void {
    this.searchForm = this.fb.group({
      startDate: [null, [Validators.required]],
      endDate: [null, [Validators.required]],
      searchQuery: [''],
      pageSize: [this.pageSize],
      sortDirection: ['Ascending']
    }, { validator: this.dateRangeValidator });
  }

  private setupFormValidation(): void {
    this.searchForm.get('startDate')?.valueChanges.subscribe(() => {
      this.searchForm.get('endDate')?.updateValueAndValidity();
    });

    this.searchForm.get('endDate')?.valueChanges.subscribe(() => {
      if (this.searchForm.get('startDate')?.value) {
        this.validateDateRange();
      }
    });
  }

  private dateRangeValidator(group: FormGroup): {[key: string]: any} | null {
    const start = group.get('startDate')?.value;
    const end = group.get('endDate')?.value;
    
    if (start && end) {
      const startDate = new Date(start);
      const endDate = new Date(end);
      
      if (startDate > endDate) {
        return { dateRange: true };
      }

      const oneYearAgo = new Date();
      oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
      if (startDate < oneYearAgo) {
        return { tooOld: true };
      }
    }
    return null;
  }

  private validateDateRange(): void {
    const startDate = this.searchForm.get('startDate')?.value;
    const endDate = this.searchForm.get('endDate')?.value;

    if (startDate && endDate) {
      const start = new Date(startDate);
      const end = new Date(endDate);
      const oneYearAgo = new Date();
      oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);

      if (start > end) {
        this.searchForm.get('endDate')?.setErrors({ dateRange: true });
      } else if (start < oneYearAgo) {
        this.searchForm.get('startDate')?.setErrors({ tooOld: true });
      } else {
        this.searchForm.get('endDate')?.setErrors(null);
        this.searchForm.get('startDate')?.setErrors(null);
      }
    }
  }

  onSearch(): void {
    if (this.searchForm.valid) {
      this.currentPage = 1;
      this.loadTransactions();
    } else {
      this.markFormGroupTouched(this.searchForm);
    }
  }

  private markFormGroupTouched(formGroup: FormGroup) {
    Object.values(formGroup.controls).forEach(control => {
      control.markAsTouched();
      if (control instanceof FormGroup) {
        this.markFormGroupTouched(control);
      }
    });
  }

  loadTransactions(): void {
    this.loading = true;
    const filter: IAdminTransactionCreatedDateFilter = {
      pageNumber: this.currentPage,
      pageSize: Number(this.searchForm.get('pageSize')?.value),
      startDate: this.searchForm.get('startDate')?.value,
      endDate: this.searchForm.get('endDate')?.value,
      searchQuery: this.searchForm.get('searchQuery')?.value,
      sortBy: this.searchForm.get('sortDirection')?.value,
      isSortAscending: this.searchForm.get('sortDirection')?.value === 'Ascending'
    };

    this.transactionService.getAllTransactions(filter).subscribe({
      next: (response) => {
        this.transactions = response.data.pageItems;
        this.totalRecords = response.data.totalRecord;
        this.hasSearchResults = true;
        this.loading = false;
        
        if (this.transactions.length === 0) {
          this.toastr.info('No transactions found for the selected criteria');
        }
      },
      error: (error) => {
        this.loading = false;
        this.toastr.error('Error loading transactions');
      }
    });
  }

  onPageChange(event: IPageChangedEvent): void {
    this.currentPage = event.page;
    this.loadTransactions();
  }

  onPageSizeChange(): void {
    this.currentPage = 1;
    this.pageSize = Number(this.searchForm.get('pageSize')?.value);
    if (this.hasSearchResults) {
      this.loadTransactions();
    }
  }

  onSortChange(): void {
    if (this.hasSearchResults) {
      this.currentPage = 1;
      this.loadTransactions();
    }
  }

  onExportToExcel(): void {
    if (!this.searchForm.valid) {
      this.toastr.error('Please fill in all required fields');
      return;
    }

    this.loading = true;
    const filter: IAdminTransactionCreatedDateFilter = {
      pageNumber: 1,
      pageSize: this.totalRecords,
      startDate: this.searchForm.get('startDate')?.value,
      endDate: this.searchForm.get('endDate')?.value,
      searchQuery: this.searchForm.get('searchQuery')?.value
    };

    this.transactionService.exportToExcel(filter).subscribe({
      next: (blob: Blob) => {
        const fileName = `Transactions_${new Date().toISOString()}.xlsx`;
        saveAs(blob, fileName);
        this.toastr.success('Export completed successfully');
        this.loading = false;
      },
      error: (error) => {
        this.loading = false;
        this.toastr.error('Error exporting to Excel');
      }
    });
  }

  get formControls() {
    return this.searchForm.controls;
  }
}

admin-created-date-transactionsponent.html:

<div class="content-header">
  <div class="container-fluid">
    <div class="row mb-2">
      <div class="col-sm-6">
        <h1 class="m-0">Admin Dashboard: {{ pageTitle }}</h1>
      </div>
      <div class="col-sm-6">
        <ol class="breadcrumb float-sm-right">
          <li class="breadcrumb-item">
            <a [routerLink]="['/admin-dashboard']">Dashboard</a>
          </li>
          <li class="breadcrumb-item active">{{ pageTitle }}</li>
        </ol>
      </div>
    </div>
  </div>
</div>
<section class="content">
  <div class="container-fluid">      
  <!-- Search Form -->
  <form [formGroup]="searchForm" (ngSubmit)="onSearch()">
    <div class="row mb-3">
      <div class="col-md-3">
        <label for="startDate">Start Date:<span style="color: red">*</span></label>
        <div class="input-group">
          <div class="input-group-prepend">
            <span class="input-group-text"
              ><i class="far fa-calendar-alt"></i
            ></span>
          </div>
          <input
            type="text"
            placeholder="DD-MM-YYYY"
            class="form-control"
            formControlName="startDate"
            bsDatepicker
            [bsConfig]="bsConfig"
            [maxDate]="maxDate"
            [minDate]="minDate"
            [readonly]="true"
            placement="bottom"
          >          
        </div>
        <div *ngIf="formControls['startDate'].touched && formControls['startDate'].errors" class="text-danger">
          <small *ngIf="formControls['startDate'].errors?.['required']">Start date is required</small>
          <small *ngIf="formControls['startDate'].errors?.['tooOld']">Date cannot be more than one year old</small>
        </div>
      </div>
      <div class="col-md-3">
        <label for="endDate">End Date:<span style="color: red">*</span></label>
        <div class="input-group">
          <div class="input-group-prepend">
            <span class="input-group-text"
              ><i class="far fa-calendar-alt"></i
            ></span>
          </div>
          <input
            type="text"
            placeholder="DD-MM-YYYY"
            class="form-control"
            formControlName="endDate"
            bsDatepicker
            [bsConfig]="bsConfig"
            [maxDate]="maxDate"
            [minDate]="minDate"
            [readonly]="true"
            placement="bottom"
          >          
        </div>
        <div *ngIf="formControls['endDate'].touched && formControls['endDate'].errors" class="text-danger">
          <small *ngIf="formControls['endDate'].errors?.['required']">End date is required</small>
          <small *ngIf="formControls['endDate'].errors?.['dateRange']">End date must be after start date</small>
        </div>
      </div>
      <div class="col-md-4">
        <label class="form-label">Search Query</label>
          <input
          type="text"
          class="form-control"
          formControlName="searchQuery"
          placeholder="Enter search term..."
        >
      </div>
      <div class="col-md-2">
        <label class="form-label">Items Per Page</label>
        <select class="form-control" formControlName="pageSize" (change)="onPageSizeChange()">
          <option *ngFor="let size of pageSizeOptions" [value]="size">{{size}}</option>
        </select>
      </div>
    </div>
    <div class="col-md-6">
      <div class="form-group d-flex justify-content-end align-items-end h-100">
        <button type="submit" class="btn btn-primary mr-2" [disabled]="!searchForm.valid || loading">
          <i class="fas fa-search mr-1"></i>
          {{ loading ? 'Searching...' : 'Search' }}
        </button>
        <button type="button" class="btn btn-success" 
                [disabled]="!searchForm.valid || !hasSearchResults || loading"
                (click)="onExportToExcel()">
          <i class="fas fa-file-excel mr-1"></i>
          {{ loading ? 'Exporting...' : 'Export to Excel' }}
        </button>
      </div>
    </div>
  </form>

    <div class="row">
      <div class="col-sm-12 col-xs-12 col-12">
        <div class="card card-danger">
          <div class="card-header">
            <h3 class="card-title">{{ pageTitle }} List</h3>
            <div class="card-tools">
              <button
                type="button"
                class="btn btn-tool"
                data-card-widget="collapse"
              >
                <i class="fas fa-minus"></i>
              </button>
            </div>
          </div>
          <div class="card-body p-0">
            <div class="table-responsive">
              <table class="table table-striped table-bordered">
                <thead>
                  <tr>
                    <th>#</th>
                    <th>Account No</th>
                    <th>Amount</th>
                    <th>Source</th>
                    <th>Transaction Type</th>
                    <th>Description</th>
                    <th>Create Date</th>
                    <th>Channel Name</th>
                  </tr>
                </thead>
                <tbody>
                  <tr *ngFor="let transaction of transactions; let i = index">
                    <td>{{((currentPage - 1) * pageSize) + i + 1}}</td>
                    <td>{{transaction.acctNo}}</td>
                    <td>{{transaction.amount | currency : " N" || "N/A" }}</td>
                    <td>{{transaction.source}}</td>
                    <td>{{transaction.transactionType}}</td>
                    <td>{{transaction.description}}</td>
                    <td>{{transaction.createDt | date : "dd-MMM-yyyy" || "N/A" }}</td>
                    <td>{{transaction.channelName}}</td>
                  </tr>
                  <tr *ngIf="!transactions?.length && hasSearchResults">
                    <td colspan="9" class="text-center">No transactions found</td>
                  </tr>
                  <tr *ngIf="!hasSearchResults">
                    <td colspan="9" class="text-center">Please select date range and search</td>
                  </tr>
                </tbody>
              </table>
            </div>
            </div>
          </div>
    <!-- Pagination -->
    <div class="card-footer clearfix" *ngIf="totalRecords > 0">
      <div class="row align-items-center">
    <!-- Pagination -->
    <div class="d-flex justify-content-center mt-3">
      <!-- <pagination
      [totalItems]="totalRecords"
      [itemsPerPage]="pageSize"
      [maxSize]="7"
      [(ngModel)]="currentPage"
      (pageChanged)="onPageChange($event)"
    ></pagination> -->
    <pagination
      [totalItems]="totalRecords"
      [itemsPerPage]="pageSize"
      [maxSize]="5"
      [(ngModel)]="currentPage"
      [boundaryLinks]="true"
      [directionLinks]="true"
      [rotate]="true"
      [firstText]="'First'"
      [lastText]="'Last'"
      [previousText]="'Previous'"
      [nextText]="'Next'"
      (pageChanged)="onPageChange($event)"
      class="mb-0"
    ></pagination>    
    </div>
        <div class="col-md-6 text-right">
          Showing {{((currentPage - 1) * pageSize) + 1}} to {{Math.min(currentPage * pageSize, totalRecords)}} of {{totalRecords}} entries          
        </div>
      </div>
    </div>
      </div>
    </div>
  </div>
</section>


Module:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
// NGX-Bootstrap
import { PaginationModule } from 'ngx-bootstrap/pagination';
import { NgxPaginationModule, PaginatePipe } from 'ngx-pagination'; //Imports NgxPaginationModule
import { ModalModule, BsModalService } from 'ngx-bootstrap/modal';
import { BsDatepickerModule, BsDatepickerConfig, BsDaterangepickerConfig } from 'ngx-bootstrap/datepicker';
import { OrderModule } from 'ngx-order-pipe';
import { AlertModule,AlertConfig } from 'ngx-bootstrap/alert';
import { PopoverModule, PopoverConfig } from 'ngx-bootstrap/popover';
import { ProgressbarModule,ProgressbarConfig } from 'ngx-bootstrap/progressbar';

ToStatusPipe

@NgModule({
  providers: [
    AlertConfig,
    BsDatepickerConfig,
    BsDaterangepickerConfig,
    BsModalService
  ],
  declarations: [

  ],
  imports: [
      NgxPaginationModule,
      AlertModule,
      PaginationModule.forRoot(),
      CommonModule,
      RouterModule,
      ReactiveFormsModule,
      FormsModule,
      BsDatepickerModule.forRoot(),
      ModalModule.forRoot(),
      OrderModule
    ],
    exports: [
      CommonModule,
      NgxPaginationModule,
      PaginationModule,
      OrderModule,
      ReactiveFormsModule,
      BsDatepickerModule
    ],
})
export class SharedModule { }
发布评论

评论列表(0)

  1. 暂无评论