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

创建Angular通用分页表格组件时MatPaginator数据渲染异常问题

问题分析与解决方案

你的问题核心在于OnPush变更检测策略和MatTable与MatPaginator未正确联动两个关键点:

  • 组件启用了ChangeDetectionStrategy.OnPush,只有当输入属性引用变化、组件内部事件触发或手动触发变更检测时,才会更新视图。从详情页返回时,若父组件的listaObjetos仅修改内容而非创建新数组(引用未变),组件不会触发视图更新。
  • 直接将原始数组绑定为表格dataSource,未使用Angular Material提供的MatTableDataSource,导致表格与分页器无联动逻辑,分页器操作时的事件恰好触发了变更检测,才让数据显示出来。

解决方案步骤

1. 改用MatTableDataSource管理表格数据

MatTableDataSource是专门为MatTable设计的数据管理类,内置分页、排序、过滤联动逻辑,能更好适配OnPush变更检测策略。

2. 完善输入属性的变更监听

通过setter监听输入属性变化,实时更新数据源,确保引用变化时触发视图更新。

3. 绑定分页器与表格数据源

通过ViewChild获取分页器实例,在组件初始化完成后绑定到数据源,实现两者联动。


修改后的组件代码

TypeScript代码

import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild, OnInit, AfterViewInit } from '@angular/core';
import { PageEvent } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';

@Component({
  selector: 'pc-tabla-paginada',
  templateUrl: './tabla-paginada.component.html',
  styles: `
    #tableDiv { overflow-x: scroll; }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TablaPaginadaComponent implements OnInit, AfterViewInit {
  public dataSource = new MatTableDataSource<any>([]);

  @ViewChild(MatPaginator) paginator!: MatPaginator;

  @Input()
  set listaObjetos(value: any[]) {
    this.dataSource.data = value || [];
    // 返回页面时重置到第一页(可选,根据业务需求调整)
    if (this.paginator) {
      this.paginator.firstPage();
    }
  }

  @Input()
  public columnasAMostrar: string[] = [];

  @Input()
  public nombreColumnas: any = {};

  @Input()
  public encabezadoTabla: string = '';

  @Input()
  public pageSizeOptions: number[] = [5, 10, 20];

  @Input()
  set pageSize(value: number) {
    if (this.paginator) {
      this.paginator.pageSize = value;
    }
  }

  @Input()
  set length(value: number) {
    if (this.paginator) {
      this.paginator.length = value;
    }
  }

  @Output()
  public page: EventEmitter<PageEvent> = new EventEmitter();

  @Output()
  public objeto: EventEmitter<any> = new EventEmitter();

  ngOnInit(): void {
    // 监听分页事件并传递给父组件
    this.dataSource.paginator?.page.subscribe((event) => {
      this.page.emit(event);
    });
  }

  ngAfterViewInit(): void {
    // 绑定分页器到数据源
    this.dataSource.paginator = this.paginator;
  }

  public enviarObjeto(elemento: any) {
    this.objeto.emit(elemento);
  }

  public esFecha(value: any): boolean {
    if (value === '' || value === null || value === undefined) return false;

    const datoProbar = new Date(value);

    if (typeof value === 'object' && datoProbar.toString() !== 'Invalid Date') {
      return true;
    }
    return false;
  }
}

HTML模板代码

<div class="row col-12">
  <div class="row col-12">
    <h2>{{ encabezadoTabla }}</h2>
    <hr>
  </div>

  <div class="mat-elevation-z8">
    <div class="mat-elevation-z8 row" id="tableDiv">
      <!-- 绑定到MatTableDataSource实例 -->
      <table mat-table [dataSource]="dataSource">

        @for (columna of columnasAMostrar; track $index) {
          <ng-container [matColumnDef] = columna >
            <th mat-header-cell *matHeaderCellDef> {{ nombreColumnas[columna] }} </th>
            <td mat-cell *matCellDef="let elemento">
              @if ( columna === 'enableEdition') {
                <button mat-raised-button
                  (click)="enviarObjeto(elemento)">
                  <mat-icon>more_horiz</mat-icon>
                  Más
                </button>
              } @else {
                @if ( esFecha(elemento[columna]) ) {
                  {{elemento[columna] | date:'medium': '':'es-ES' }}
                } @else {
                  {{elemento[columna]}}
                }
              }
            </td>
          </ng-container>
        }

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

      </table>
    </div>
    <div class="row">
      <mat-paginator
        [pageSizeOptions] = "pageSizeOptions"
        showFirstLastButtons
        aria-label="Lista de Pedidos">
      </mat-paginator>
    </div>
  </div>
</div>

父组件注意事项

从详情页返回时,确保父组件的listaObjetos是新数组引用(比如使用扩展运算符[...newData]或Array.from()),这样才能触发子组件的setter更新数据源:

// 父组件获取数据后创建新数组
this.listaObjetos = [...fetchedData];

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 19:38:17