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

基于页面可容纳项调整Angular Material分页功能

自动适配容器高度的Mat Table分页实现

我之前刚好遇到过一模一样的需求,折腾了一阵终于搞定了!核心思路就是计算容器的可用高度,减去表头、分页器的占用空间,再除以单一行的高度,得到每页能容纳的行数,然后动态设置给MatPaginator,同时监听窗口或容器大小变化,实时更新分页参数。

实现步骤拆解

    1. 获取容器、表头、分页器的DOM元素引用,计算各自高度
    1. 编写分页数量计算逻辑,确保可用空间刚好放下整行数据
    1. 监听窗口resize和容器尺寸变化,触发重新计算
    1. 处理组件生命周期,避免内存泄漏

具体代码实现

1. 组件类代码

import { Component, ElementRef, ViewChild, OnInit, AfterViewInit, OnDestroy } from '@angular/core';
import { MatTable } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';

@Component({
  selector: 'app-auto-paginate-table',
  templateUrl: './auto-paginate-table.component.html',
  styleUrls: ['./auto-paginate-table.component.scss']
})
export class AutoPaginateTableComponent implements OnInit, AfterViewInit, OnDestroy {
  displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
  dataSource = new MatTableDataSource<PeriodicElement>(ELEMENT_DATA);

  @ViewChild('tableContainer') tableContainer!: ElementRef<HTMLElement>;
  @ViewChild(MatTable) table!: MatTable<PeriodicElement>;
  @ViewChild(MatPaginator) paginator!: MatPaginator;

  private resizeObserver!: ResizeObserver;
  private debounceTimeout?: number;

  ngOnInit(): void {
    this.dataSource.paginator = this.paginator;
  }

  ngAfterViewInit(): void {
    // 初始化时计算一次分页数量
    this.calculatePageSize();
    
    // 监听窗口resize(加防抖避免频繁计算)
    window.addEventListener('resize', this.handleResize.bind(this));
    
    // 监听容器本身的尺寸变化(比如父容器拖拽调整大小)
    this.resizeObserver = new ResizeObserver(() => {
      this.calculatePageSize();
    });
    this.resizeObserver.observe(this.tableContainer.nativeElement);
  }

  ngOnDestroy(): void {
    window.removeEventListener('resize', this.handleResize.bind(this));
    this.resizeObserver?.disconnect();
    if (this.debounceTimeout) {
      clearTimeout(this.debounceTimeout);
    }
  }

  private handleResize(): void {
    // 防抖处理,200ms内只执行一次
    if (this.debounceTimeout) {
      clearTimeout(this.debounceTimeout);
    }
    this.debounceTimeout = window.setTimeout(() => {
      this.calculatePageSize();
    }, 200);
  }

  private calculatePageSize(): void {
    if (!this.tableContainer || !this.paginator) return;

    const container = this.tableContainer.nativeElement;
    const containerHeight = container.offsetHeight;

    // 获取表头高度
    const headerRow = container.querySelector('.mat-mdc-table-header-row');
    const headerHeight = headerRow ? headerRow.offsetHeight : 0;

    // 获取分页器高度
    const paginatorHeight = this.paginator._elementRef.nativeElement.offsetHeight;

    // 预留一些边距(根据你的UI调整,比如表格的内边距、间距)
    const extraPadding = 16;

    // 获取单一行的高度:如果行高固定可以直接写死,动态行高就取第一行的实际高度
    let rowHeight = 48; // 默认行高
    const firstRow = container.querySelector('.mat-mdc-row');
    if (firstRow) {
      rowHeight = firstRow.offsetHeight;
    }

    // 计算可用高度:容器总高 - 表头 - 分页器 - 预留边距
    const availableHeight = containerHeight - headerHeight - paginatorHeight - extraPadding;

    // 计算每页行数,至少保留1行
    const calculatedPageSize = Math.max(Math.floor(availableHeight / rowHeight), 1);

    // 只有当分页数量变化时才更新,避免不必要的视图刷新
    if (this.paginator.pageSize !== calculatedPageSize) {
      this.paginator.pageSize = calculatedPageSize;
      // 重置到第一页,避免当前页索引超出新的总页数
      this.paginator.firstPage();
      // 通知表格重新渲染数据
      this.table.renderRows();
    }
  }
}

interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

const ELEMENT_DATA: PeriodicElement[] = [
  {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
  {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
  {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
  {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
  {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
  {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
  {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
  {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
];

2. 模板代码

<div #tableContainer class="table-container">
  <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
    <!-- Position Column -->
    <ng-container matColumnDef="position">
      <th mat-header-cell *matHeaderCellDef> No. </th>
      <td mat-cell *matCellDef="let element"> {{element.position}} </td>
    </ng-container>

    <!-- Name Column -->
    <ng-container matColumnDef="name">
      <th mat-header-cell *matHeaderCellDef> Name </th>
      <td mat-cell *matCellDef="let element"> {{element.name}} </td>
    </ng-container>

    <!-- Weight Column -->
    <ng-container matColumnDef="weight">
      <th mat-header-cell *matHeaderCellDef> Weight </th>
      <td mat-cell *matCellDef="let element"> {{element.weight}} </td>
    </ng-container>

    <!-- Symbol Column -->
    <ng-container matColumnDef="symbol">
      <th mat-header-cell *matHeaderCellDef> Symbol </th>
      <td mat-cell *matCellDef="let element"> {{element.symbol}} </td>
    </ng-container>

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

  <!-- 隐藏手动选择分页数量的选项,因为我们要自动计算 -->
  <mat-paginator [pageSizeOptions]="[]" showFirstLastButtons></mat-paginator>
</div>

3. 样式代码

.table-container {
  height: 100%; /* 必须确保容器有确定的高度,比如父容器设置height: 100%或者固定高度 */
  overflow: hidden; /* 彻底隐藏滚动条,因为我们已经适配了高度 */
  padding: 8px; /* 可选,根据你的UI调整 */
}

.mat-mdc-table {
  width: 100%;
}

关键注意事项

  • 容器高度必须确定:如果父容器没有设置高度,offsetHeight会返回0,计算就会失效。可以用flex布局让容器占满可用空间,或者设置固定高度。
  • 动态行高处理:如果你的表格行高是动态的(比如内容换行),一定要在数据加载完成后再调用calculatePageSize,否则第一行可能还没渲染,获取不到正确的行高。
  • 防抖优化:窗口resize事件会频繁触发,加防抖可以避免频繁计算导致性能问题。
  • 分页器设置:把pageSizeOptions设为空数组,禁用用户手动选择每页数量,确保自动计算的逻辑生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:35:09