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

如何为可复用Angular Material表格添加服务端分页、筛选与排序?

实现Angular Material可复用表格的服务端分页、筛选与排序

一、前端(Angular)部分修改

1. 更新TableColumn接口

补充排序相关配置,让组件支持列级排序控制:

export interface TableColumn<T> {
  columnDef: string;
  header: string;
  cell: (element: T) => string;
  // 新增排序属性
  sortable?: boolean; // 是否允许该列排序
  defaultSortDirection?: 'asc' | 'desc'; // 可选,默认排序方向
}

2. 修改table.component.ts

添加分页、筛选、排序的状态管理,封装数据加载逻辑,并通过输入属性接收业务端的API调用方法:

import { Component, Input, ViewChild, OnInit } from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { MatSort, Sort } from '@angular/material/sort';
import { Observable } from 'rxjs';
import { TableColumn } from './table-column.interface';

@Component({
  selector: 'app-reusable-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.css']
})
export class TableComponent<T> implements OnInit {
  @Input() columns: TableColumn<T>[] = [];
  @Input() fetchData: (params: any) => Observable<{ data: T[], totalCount: number }>;
  
  @ViewChild(MatPaginator) paginator!: MatPaginator;
  @ViewChild(MatSort) sort!: MatSort;

  displayedColumns: string[] = [];
  filterValue = '';
  totalItems = 0;
  dataSource = new MatTableDataSource<T>();

  ngOnInit(): void {
    this.displayedColumns = this.columns.map(col => col.columnDef);
    this.loadData();
  }

  // 核心数据加载方法,聚合所有请求参数
  loadData(): void {
    const params = {
      pageNumber: this.paginator?.pageIndex + 1 || 1, // 适配后端从1开始的页码规则
      pageSize: this.paginator?.pageSize || 10,
      sortField: this.sort?.active || this.columns.find(col => col.sortable)?.columnDef || 'id',
      sortDirection: this.sort?.direction || 'asc',
      filter: this.filterValue
    };

    this.fetchData(params).subscribe(result => {
      this.dataSource.data = result.data;
      this.totalItems = result.totalCount;
    });
  }

  // 分页切换事件
  onPageChange(event: PageEvent): void {
    this.loadData();
  }

  // 排序切换事件,校验列是否允许排序
  onSortChange(event: Sort): void {
    const targetColumn = this.columns.find(col => col.columnDef === event.active);
    if (!targetColumn?.sortable) {
      this.sort.active = '';
      this.sort.direction = '';
      return;
    }
    this.loadData();
  }

  // 筛选输入事件,重置分页到第一页
  applyFilter(event: Event): void {
    this.filterValue = (event.target as HTMLInputElement).value.trim().toLowerCase();
    if (this.paginator) this.paginator.pageIndex = 0;
    this.loadData();
  }
}

3. 修改table.component.html

添加筛选框、分页器,并给可排序列绑定排序表头:

<!-- 筛选区域 -->
<div class="table-filter">
  <mat-form-field appearance="outline">
    <mat-label>筛选</mat-label>
    <input matInput (keyup)="applyFilter($event)" placeholder="输入关键词">
  </mat-form-field>
</div>

<!-- 表格主体 -->
<table mat-table [dataSource]="dataSource" matSort (matSortChange)="onSortChange($event)">
  <ng-container *ngFor="let column of columns" [matColumnDef]="column.columnDef">
    <th mat-header-cell *matHeaderCellDef mat-sort-header="{{column.sortable ? column.columnDef : ''}}">
      {{column.header}}
    </th>
    <td mat-cell *matCellDef="let element"> {{column.cell(element)}} </td>
  </ng-container>

  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>

<!-- 分页器 -->
<mat-paginator
  [length]="totalItems"
  [pageSizeOptions]="[5, 10, 20]"
  showFirstLastButtons
  (page)="onPageChange($event)"
></mat-paginator>

4. 父组件使用示例

以用户列表为例,传入列配置和自定义API调用:

// user-list.component.ts
import { Component } from '@angular/core';
import { UserService } from './user.service';
import { TableColumn } from '../reusable-table/table-column.interface';
import { User } from './user.model';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html'
})
export class UserListComponent {
  columns: TableColumn<User>[] = [
    { columnDef: 'id', header: 'ID', cell: (user) => user.id.toString(), sortable: true },
    { columnDef: 'name', header: '姓名', cell: (user) => user.name, sortable: true },
    { columnDef: 'email', header: '邮箱', cell: (user) => user.email }
  ];

  constructor(private userService: UserService) {}

  // 传递给复用表格的API调用方法
  fetchUsers(params: any) {
    return this.userService.getUsers(
      params.pageNumber,
      params.pageSize,
      params.sortField,
      params.sortDirection,
      params.filter
    );
  }
}

对应的父组件模板:

<!-- user-list.component.html -->
<app-reusable-table
  [columns]="columns"
  [fetchData]="fetchUsers.bind(this)"
></app-reusable-table>

二、后端(ASP.NET Core + EF Core)部分修改

1. 创建分页结果模型

统一返回数据和总条数格式:

public class PagedResult<T>
{
    public List<T> Data { get; set; } = new List<T>();
    public int TotalCount { get; set; }
}

2. 修改API接口实现

接收前端参数,依次执行筛选、排序、分页逻辑:

[HttpGet]
public async Task<IActionResult> GetUsers(
    int pageNumber = 1,
    int pageSize = 10,
    string? sortField = "Id",
    string? sortDirection = "asc",
    string? filter = null)
{
    var query = _context.Users.AsQueryable();

    // 筛选逻辑:多字段模糊匹配
    if (!string.IsNullOrEmpty(filter))
    {
        query = query.Where(u => u.Name.Contains(filter) || u.Email.Contains(filter));
    }

    // 排序逻辑:类型安全的字段枚举,避免反射风险
    query = sortField?.ToLower() switch
    {
        "name" => sortDirection?.ToLower() == "desc" ? query.OrderByDescending(u => u.Name) : query.OrderBy(u => u.Name),
        "email" => sortDirection?.ToLower() == "desc" ? query.OrderByDescending(u => u.Email) : query.OrderBy(u => u.Email),
        _ => sortDirection?.ToLower() == "desc" ? query.OrderByDescending(u => u.Id) : query.OrderBy(u => u.Id)
    };

    // 分页逻辑
    var totalCount = await query.CountAsync();
    var data = await query
        .Skip((pageNumber - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync();

    return Ok(new PagedResult<User> { Data = data, TotalCount = totalCount });
}

3. 可选:封装通用分页扩展方法

如果多个API需要分页逻辑,可以封装复用:

public static class QueryableExtensions
{
    public static async Task<PagedResult<T>> ToPagedResultAsync<T>(this IQueryable<T> query, int pageNumber, int pageSize)
    {
        var totalCount = await query.CountAsync();
        var data = await query.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
        return new PagedResult<T> { Data = data, TotalCount = totalCount };
    }
}

使用时可简化分页代码:

var pagedResult = await query.ToPagedResultAsync(pageNumber, pageSize);
return Ok(pagedResult);

三、关键注意事项

  • 参数对齐:前端分页器pageIndex从0开始,后端通常页码从1开始,需做+1转换。
  • 排序安全:避免直接用反射拼接排序逻辑,通过枚举字段的方式防止无效字段或SQL注入风险。
  • 性能优化:给筛选、排序字段添加数据库索引,避免大数据量下的查询性能问题。
  • 筛选扩展:可根据业务需求扩展多字段精确匹配、范围筛选等逻辑。

内容的提问来源于stack exchange,提问作者Alex Wright

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 18:40:32