You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于API数据实现ng-bootstrap表格的排序、搜索过滤与分页?

解决Ng-bootstrap表格排序、过滤、分页适配动态API数据的问题

我完全懂你遇到的痛点——官方示例用的是现成的静态数据数组,直接套用到异步加载的API数据上肯定会踩坑,毕竟数据不是一开始就存在的。咱们一步步调整代码,把排序、搜索过滤和分页功能都稳稳装上:

第一步:先把必要的Ng-bootstrap模块导入到位

首先,在你的模块文件(比如AdminModule或者AppModule)里,得把表格相关的功能模块加进去,不然指令会失效:

import { NgbSortModule, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';

@NgModule({
  imports: [
    // ...其他你已经导入的模块
    NgbSortModule,
    NgbPaginationModule
  ]
})
export class AdminModule { }

第二步:修改Component代码,适配异步数据+实现功能逻辑

咱们要给TS代码加上排序、过滤、分页的变量和方法,同时处理好API数据的异步加载时机:

import { Component, OnInit, ViewChild } from '@angular/core';
import { NgxSpinnerService } from 'ngx-spinner';
import { TransactionsService } from '../transactions.service';
import { Transactions, Career, Faculty, RoleType } from 'src/assets/models';
import { RegisterService } from '../register.service';
import { AlertService } from '../alert.service';
import { NgbSort, NgbSortEvent } from '@ng-bootstrap/ng-bootstrap';

@Component({
  selector: 'app-admin',
  templateUrl: './admin.component.html',
  styleUrls: ['./admin.component.scss']
})
export class AdminComponent implements OnInit {
  content: Transactions;
  // 新增:用于过滤、分页的中间数据变量
  filteredTransactions: any[] = [];
  currentPageTransactions: any[] = [];
  searchText = '';
  page = 1;
  pageSize = 10;
  totalItems = 0;

  analytic = false;
  uniFaculties: Faculty;
  uniCareers: Career;
  uniUsers: RoleType;

  // 获取排序组件的DOM引用,用来绑定排序事件
  @ViewChild(NgbSort) sort: NgbSort;

  constructor(
    private modalService: NgbModal,
    private spinner: NgxSpinnerService,
    private alertService: AlertService,
    private transactionService: TransactionsService
  ) { }

  ngOnInit() {
    this.spinner.show();
    this.transactionService.getAllTransactions()
      .subscribe(res => {
        this.content = res;
        // 数据加载完成后,初始化过滤数组(用副本避免修改原数据)
        this.filteredTransactions = [...this.content.transactions];
        this.totalItems = this.filteredTransactions.length;
        // 初始化当前页展示的数据
        this.updateCurrentPageData();
        console.log(this.content); // 测试用的日志
        this.spinner.hide(); // 把加载动画隐藏移到这里,确保数据加载完再隐藏
      });
  }

  // 搜索过滤方法:根据输入框内容过滤交易数据
  onSearch() {
    if (!this.searchText.trim()) {
      this.filteredTransactions = [...this.content.transactions];
    } else {
      const searchTerm = this.searchText.toLowerCase();
      this.filteredTransactions = this.content.transactions.filter(transaction => 
        transaction.trx_ID.toString().includes(searchTerm) ||
        transaction.user_ID.toString().includes(searchTerm) ||
        transaction.trx_type.toLowerCase().includes(searchTerm) ||
        transaction.description.toLowerCase().includes(searchTerm) ||
        transaction.amount.toString().includes(searchTerm)
      );
    }
    this.totalItems = this.filteredTransactions.length;
    this.page = 1; // 搜索后自动回到第一页
    this.updateCurrentPageData();
  }

  // 排序处理方法:根据表格头的排序指令处理数据
  onSort(event: NgbSortEvent) {
    if (!event.active || event.direction === '') {
      this.filteredTransactions = [...this.content.transactions];
      this.updateCurrentPageData();
      return;
    }

    this.filteredTransactions.sort((a, b) => {
      const isAsc = event.direction === 'asc';
      switch (event.active) {
        case 'trxId': return compare(a.trx_ID, b.trx_ID, isAsc);
        case 'userId': return compare(a.user_ID, b.user_ID, isAsc);
        case 'type': return compare(a.trx_type, b.trx_type, isAsc);
        case 'date': return compare(new Date(a.date), new Date(b.date), isAsc);
        case 'description': return compare(a.description, b.description, isAsc);
        case 'amount': return compare(a.amount, b.amount, isAsc);
        default: return 0;
      }
    });
    this.updateCurrentPageData();
  }

  // 更新当前页要展示的数据
  updateCurrentPageData() {
    const startIndex = (this.page - 1) * this.pageSize;
    this.currentPageTransactions = this.filteredTransactions.slice(startIndex, startIndex + this.pageSize);
  }

  // 分页切换事件
  onPageChange(page: number) {
    this.page = page;
    this.updateCurrentPageData();
  }
}

// 辅助排序的工具函数,统一处理不同类型数据的排序逻辑
function compare(a: number | string | Date, b: number | string | Date, isAsc: boolean) {
  return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}

第三步:修改HTML模板,添加搜索框、排序指令和分页组件

把原来的表格模板调整一下,把Ng-bootstrap的指令、搜索输入框和分页组件加进去:

<div class="fluid-container admin-transactions" *ngIf="content">
  <h1><span class="badge badge-info header-transactions"><i class="fas fa-exchange-alt"></i> Transacciones</span></h1>
  
  <!-- 搜索输入框 -->
  <div class="mb-3">
    <input 
      type="text" 
      class="form-control" 
      placeholder="Buscar transacción..." 
      [(ngModel)]="searchText"
      (input)="onSearch()"
    >
  </div>

  <!-- 绑定排序指令的表格 -->
  <table class="table table-hover fixed_header" ngbSort (sortChange)="onSort($event)">
    <thead class="thead table-info">
      <tr class="row table-info">
        <th 
          class="col-1 d-flex justify-content-center" 
          scope="col"
          ngbSortHeader="trxId"
        >ID de Transacción</th>
        <th 
          class="col-1 d-flex justify-content-center" 
          scope="col"
          ngbSortHeader="userId"
        >ID de Usuario</th>
        <th 
          class="col-1 d-flex justify-content-center" 
          scope="col"
          ngbSortHeader="type"
        >Tipo</th>
        <th 
          class="col-4 d-flex justify-content-center" 
          scope="col"
          ngbSortHeader="date"
        >Fecha</th>
        <th 
          class="col-3 d-flex justify-content-center" 
          scope="col"
          ngbSortHeader="description"
        >Descripción</th>
        <th 
          class="col-2 d-flex justify-content-center" 
          scope="col"
          ngbSortHeader="amount"
        >Monto</th>
      </tr>
    </thead>
    <tbody>
      <!-- 遍历当前页的数据,而不是原数据 -->
      <tr class="row" *ngFor='let row of currentPageTransactions'>
        <td class="col-1 d-flex justify-content-center id">{{ row.trx_ID }}</td>
        <td class="col-1 d-flex justify-content-center id">{{ row.user_ID }}</td>
        <td class="col-1 d-flex justify-content-center trx" [ngClass]="{'custom-class': row.trx_type === 'RCH' && row.amount !== 0, 'default-color': row.trx_type !== 'RCH'}">{{ row.trx_type}}</td>
        <td class="col-4 d-flex justify-content-center date">{{ row.date | date:'long' }} </td>
        <td class="col-3 d-flex justify-content-center description">{{ row.description }}</td>
        <td class="col-2 d-flex justify-content-center amount" [ngClass]="{'rch-class': row.trx_type === 'RCH' && row.amount !== 0 , 'pay-class': row.trx_type !== 'RCH'}">{{ row.amount | currency }}</td>
      </tr>
    </tbody>
  </table>

  <!-- 分页组件 -->
  <div class="d-flex justify-content-center mt-3">
    <ngb-pagination
      [(page)]="page"
      [pageSize]="pageSize"
      [collectionSize]="totalItems"
      (pageChange)="onPageChange($event)"
      [maxSize]="5"
      [rotate]="true"
      [ellipses]="true"
    ></ngb-pagination>
  </div>
</div>
<hr>

几个关键细节提醒

  • 异步数据时机:把spinner.hide()移到subscribe回调里,确保只有当API数据加载完成后才隐藏加载动画,避免数据还没回来就渲染表格导致的空数据问题。
  • 数据副本:处理过滤和排序时用的是原数据的副本([...this.content.transactions]),这样不会修改API返回的原始数据,避免后续逻辑出错。
  • 排序字段映射:ngbSortHeader的值要和onSort方法里的switch分支对应上,确保每一列的排序逻辑都能正确触发。

内容的提问来源于stack exchange,提问作者Luis Yao

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:25:02