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

如何在Angular的Ag-Grid中实现行增删的撤销/重做功能?

解决方案:扩展Ag-Grid实现行增删的撤销/重做

核心思路

Ag-Grid原生撤销重做仅覆盖单元格编辑、剪贴板操作,行增删需要手动维护操作历史栈,记录每一次行操作的关键信息(操作类型、行数据、行标识/索引),再通过自定义方法触发数据回滚。

具体实现步骤

1. 定义操作历史数据结构

在组件类中声明栈结构与操作类型,用于存储行操作快照:

// 行操作类型枚举
enum RowOperationType {
  INSERT = 'insert',
  DELETE = 'delete'
}

// 操作历史项接口
interface RowOperationHistory {
  type: RowOperationType;
  rowData: any;       // 操作的行数据(需包含唯一标识如id)
  rowPosition: number; // 操作时的行插入/所在索引
}

// 维护撤销、重做两个栈
private undoStack: RowOperationHistory[] = [];
private redoStack: RowOperationHistory[] = [];

2. 封装带历史记录的行增删方法

不要直接调用Ag-Grid原生的updateRowData,改用自定义方法记录操作:

新增行方法

addRow(newRow: any) {
  const insertIndex = this.gridApi.getDisplayedRowCount();
  // 执行新增
  this.gridApi.updateRowData({ add: [newRow], addIndex: insertIndex });
  // 记录到撤销栈,同时清空重做栈
  this.undoStack.push({
    type: RowOperationType.INSERT,
    rowData: newRow,
    rowPosition: insertIndex
  });
  this.redoStack = [];
}

删除行方法

deleteSelectedRow() {
  const selectedRows = this.gridApi.getSelectedRows();
  if (selectedRows.length === 0) return;

  const targetRow = selectedRows[0];
  // 获取行的原始索引(若有排序/过滤,建议用rowNode.id替代可视化索引)
  const rowNode = this.gridApi.getRowNode(targetRow.id);
  const deleteIndex = rowNode.rowIndex;

  // 执行删除
  this.gridApi.updateRowData({ remove: [targetRow] });
  // 记录到撤销栈,清空重做栈
  this.undoStack.push({
    type: RowOperationType.DELETE,
    rowData: targetRow,
    rowPosition: deleteIndex
  });
  this.redoStack = [];
}

3. 实现撤销与重做逻辑

撤销操作

undoRowOp() {
  if (this.undoStack.length === 0) return;

  const lastOp = this.undoStack.pop();
  if (!lastOp) return;

  switch (lastOp.type) {
    case RowOperationType.INSERT:
      // 撤销新增:删除对应行
      this.gridApi.updateRowData({ remove: [lastOp.rowData] });
      this.redoStack.push(lastOp);
      break;
    case RowOperationType.DELETE:
      // 撤销删除:在原位置插入该行
      this.gridApi.updateRowData({ add: [lastOp.rowData], addIndex: lastOp.rowPosition });
      this.redoStack.push(lastOp);
      break;
  }
}

重做操作

redoRowOp() {
  if (this.redoStack.length === 0) return;

  const lastRedoOp = this.redoStack.pop();
  if (!lastRedoOp) return;

  switch (lastRedoOp.type) {
    case RowOperationType.INSERT:
      // 重做新增:再次插入该行
      this.gridApi.updateRowData({ add: [lastRedoOp.rowData], addIndex: lastRedoOp.rowPosition });
      this.undoStack.push(lastRedoOp);
      break;
    case RowOperationType.DELETE:
      // 重做删除:再次删除该行
      this.gridApi.updateRowData({ remove: [lastRedoOp.rowData] });
      this.undoStack.push(lastRedoOp);
      break;
  }
}

4. 模板绑定与原生功能联动

在component.html中绑定自定义按钮,并保留原生撤销重做配置:

<!-- 自定义行操作撤销/重做按钮 -->
<button (click)="undoRowOp()" [disabled]="undoStack.length === 0">撤销行操作</button>
<button (click)="redoRowOp()" [disabled]="redoStack.length === 0">重做行操作</button>

<ag-grid-angular
  style="width: 100%; height: 500px;"
  class="ag-theme-alpine"
  [rowData]="rowData"
  [columnDefs]="columnDefs"
  [undoRedoCellEditing]="true"
  [undoRedoClipboard]="true"
  (gridReady)="onGridReady($event)"
></ag-grid-angular>

若需要和原生单元格编辑撤销重做联动,可监听原生栈变化清空自定义栈:

onGridReady(params: GridReadyEvent) {
  this.gridApi = params.api;
  // 原生撤销栈变化时,清空自定义行操作栈避免冲突
  this.gridApi.addEventListener('undoRedoStackChanged', () => {
    this.undoStack = [];
    this.redoStack = [];
  });
}

注意事项

  • 行数据必须包含唯一标识(如id),确保增删操作能精准匹配目标行
  • 若使用排序、过滤功能,建议用rowNode.id替代可视化rowIndex,避免索引偏移导致回滚位置错误
  • 批量行操作需调整历史记录结构,支持存储批量数据与批量回滚

内容的提问来源于stack exchange,提问作者Donald Hunter Kengue

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 09:05:28