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

如何在Angular 15中实现HTML表格单元格的横竖合并?

Angular 15动态表格实现多方向相邻单元格合并

需求说明

需要在Angular 15的动态HTML表格中,实现所有值相同的相邻单元格合并,支持垂直(上下)和水平(左右)两个方向,不受列或行的限制。例如:

string string string 
string string string    =>    string
string string string

合并效果示例

现有基础代码

HTML结构

<!-- HTML File -->
<div class="table-wrapper">
    <table class="fl-table" aria-describedby="table-description">
    <tbody>
        <tr *ngFor="let row of tableData">
        <ng-container *ngFor="let columnName of columnNames">
            <td>{{ row[columnName] }}</td>
        </ng-container>
        </tr>
    </tbody>
    </table>
</div>

基础TS代码

columnNames: any[] = [];
tableData: any[] = [];

getColumnNames(data: any) {
  if (data.length > 0) {
    this.columnNames = Object.keys(data[0]);
  }
}

尝试过的方案(未达预期)

当前尝试的mergeCells方法仅实现了部分垂直合并,效果不符合预期:
当前实现效果

尝试的TS代码

ngAfterViewInit(): void {
  this.mergeCells();
}

mergeCells(): void {
const tableElement: HTMLTableElement = this.elementRef.nativeElement.querySelector('table');

let headerCells: HTMLTableCellElement[] = [];

for (let rowIndex = 0; rowIndex < tableElement.rows.length; rowIndex++) {
    const row = tableElement.rows[rowIndex];

    for (let colIndex = 0; colIndex < row.cells.length; colIndex++) {
    const cell = row.cells[colIndex];

    if (cell.innerText.trim() === '') {
        continue;
    }

    if (headerCells[colIndex] === undefined || cell.innerText !== headerCells[colIndex].innerText) {
        headerCells[colIndex] = cell;
    } else {
        headerCells[colIndex].rowSpan++;
        cell.remove();
    }
    }
}
}

参考表格数据

tableData: any[] = [
    {
      "0": "",
      "1": "",
      "2": "key1",
      "3": "key1",
      "4": "key2",
      "5": "key2",
      "6": "value5",
      "7": "value5",
    },
    {
      "0": "",
      "1": "",
      "2": "key1",
      "3": "key1",
      "4": "key2",
      "5": "key2",
      "6": "value5",
      "7": "value5",
    },
    {
      "0": "",
      "1": "",
      "2": "Count",
      "3": "Amount",
      "4": "Count",
      "5": "Amount",
      "6": "Count",
      "7": "Amount",
    },
    {
      "0": "Loaded Data",
      "1": "All",
      "2": 245205,
      "3": "495693992.68",
      "4": 238112,
      "5": "475560898.63",
      "6": 247642,
      "7": "53858508052.00",
    },
    {
      "0": "Status",
      "1": "value1",
      "2": 233398,
      "3": "464734379.76",
      "4": 233398,
      "5": "464734379.76",
      "6": 233398,
      "7": "46473437976.00",
    },
    {
      "0": "Status",
      "1": "value2",
      "2": 11807,
      "3": "30959612.92",
      "4": 4714,
      "5": "10826518.87",
      "6": 14244,
      "7": "7385070076.00",
    },
    {
      "0": "key2",
      "1": "value1",
      "2": 237591,
      "3": "473886210.33",
      "4": "",
      "5": "",
      "6": 233900,
      "7": "46634667766.00",
    },
    {
      "0": "key2",
      "1": "value2",
      "2": 7614,
      "3": "21807782.35",
      "4": "",
      "5": "",
      "6": 13742,
      "7": "7223840286.00",
    },
    {
      "0": "key2",
      "1": "value3",
      "2": 7084,
      "3": "20074807.55",
      "4": "",
      "5": "",
      "6": 13584,
      "7": "7173509166.00",
    },
    {
      "0": "key2",
      "1": "value4",
      "2": 530,
      "3": "1732974.80",
      "4": "",
      "5": "",
      "6": 158,
      "7": "50331120.00",
    },
    {
      "0": "value5",
      "1": "value1",
      "2": 233398,
      "3": "464734379.76",
      "4": 233900,
      "5": "466346677.66",
      "6": "",
      "7": "",
    },
    {
      "0": "value5",
      "1": "value2",
      "2": 11807,
      "3": "30959612.92",
      "4": 4212,
      "5": "9214220.97",
      "6": "",
      "7": "",
    },
    {
      "0": "value5",
      "1": "value3",
      "2": 3695,
      "3": "7687836.58",
      "4": 3565,
      "5": "7224948.37",
      "6": "",
      "7": "",
    },
    {
      "0": "value5",
      "1": "value4",
      "2": 8112,
      "3": "23271776.34",
      "4": 647,
      "5": "1989272.60",
      "6": "",
      "7": "",
    },
    {
      "0": "key1",
      "1": "value1",
      "2": "",
      "3": "",
      "4": 237591,
      "5": "473886210.33",
      "6": 233398,
      "7": "46473437976.00",
    },
    {
      "0": "key1",
      "1": "value2",
      "2": "",
      "3": "",
      "4": 521,
      "5": "1674688.30",
      "6": 14244,
      "7": "7385070076.00",
    },
    {
      "0": "key1",
      "1": "value3",
      "2": "",
      "3": "",
      "4": 1,
      "5": "2000.00",
      "6": 6151,
      "7": "4983865494.00",
    },
    {
      "0": "key1",
      "1": "value4",
      "2": "",
      "3": "",
      "4": 520,
      "5": "1672688.30",
      "6": 8093,
      "7": "2401204582.00",
    },
  ]

解决方案

要实现双向合并,需先预处理数据计算每个单元格的rowSpan和colSpan,再基于合并信息渲染页面,避免直接操作DOM的局限性。

步骤1:预处理数据,计算合并跨度

在组件中添加方法,遍历表格数据标记每个单元格的合并信息:

// 定义单元格合并信息的接口
interface CellMergeInfo {
  value: any;
  rowSpan: number;
  colSpan: number;
  isVisible: boolean; // 标记是否需要渲染该单元格
}

mergedTableData: CellMergeInfo[][] = [];

ngOnInit(): void {
  this.getColumnNames(this.tableData);
  this.calculateCellMerges();
}

calculateCellMerges(): void {
  const rows = this.tableData.length;
  const cols = this.columnNames.length;
  
  // 初始化合并信息数组
  this.mergedTableData = this.tableData.map(row => 
    this.columnNames.map(col => ({
      value: row[col],
      rowSpan: 1,
      colSpan: 1,
      isVisible: true
    }))
  );

  // 处理垂直合并(上下相邻)
  for (let col = 0; col < cols; col++) {
    let currentRow = 0;
    while (currentRow < rows) {
      let nextRow = currentRow + 1;
      // 找到所有连续相同值的行
      while (nextRow < rows && 
             this.mergedTableData[nextRow][col].value === this.mergedTableData[currentRow][col].value &&
             this.mergedTableData[nextRow][col].value !== '') {
        this.mergedTableData[currentRow][col].rowSpan++;
        this.mergedTableData[nextRow][col].isVisible = false;
        nextRow++;
      }
      currentRow = nextRow;
    }
  }

  // 处理水平合并(左右相邻)
  for (let row = 0; row < rows; row++) {
    let currentCol = 0;
    while (currentCol < cols) {
      if (!this.mergedTableData[row][currentCol].isVisible) {
        currentCol++;
        continue;
      }
      let nextCol = currentCol + 1;
      // 找到所有连续相同值的列
      while (nextCol < cols && 
             this.mergedTableData[row][nextCol].value === this.mergedTableData[row][currentCol].value &&
             this.mergedTableData[row][nextCol].value !== '' &&
             this.mergedTableData[row][nextCol].isVisible) {
        this.mergedTableData[row][currentCol].colSpan++;
        this.mergedTableData[row][nextCol].isVisible = false;
        nextCol++;
      }
      currentCol = nextCol;
    }
  }
}

步骤2:修改HTML模板,根据合并信息渲染

<div class="table-wrapper">
    <table class="fl-table" aria-describedby="table-description">
    <tbody>
        <tr *ngFor="let row of mergedTableData">
        <td *ngFor="let cell of row"
            [rowSpan]="cell.rowSpan"
            [colSpan]="cell.colSpan"
            *ngIf="cell.isVisible">
            {{ cell.value }}
        </td>
        </tr>
    </tbody>
    </table>
</div>

说明

  • 先处理垂直合并,再处理水平合并,确保已被垂直合并隐藏的单元格不参与水平合并计算
  • 空值单元格不参与合并,避免不必要的空白合并
  • 通过isVisible标记控制单元格是否渲染,避免重复渲染被合并的单元格

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 20:59:50