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
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.
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 { }
