基于页面可容纳项调整Angular Material分页功能
自动适配容器高度的Mat Table分页实现
我之前刚好遇到过一模一样的需求,折腾了一阵终于搞定了!核心思路就是计算容器的可用高度,减去表头、分页器的占用空间,再除以单一行的高度,得到每页能容纳的行数,然后动态设置给MatPaginator,同时监听窗口或容器大小变化,实时更新分页参数。
实现步骤拆解
- 获取容器、表头、分页器的DOM元素引用,计算各自高度
- 编写分页数量计算逻辑,确保可用空间刚好放下整行数据
- 监听窗口resize和容器尺寸变化,触发重新计算
- 处理组件生命周期,避免内存泄漏
具体代码实现
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
相关产品推荐
相关产品推荐

