autoGroupColumnDef 更改时 AgGridReact 组件未更新

AgGridReact component not updating when autoGroupColumnDef changes

我正在使用带有 React 的 ag-Grid 来测试他们的企业 row grouping 功能。我想在运行时更改行分组列名称,但我无法做到这一点。

当我更改 AgGridReact 的 columnDefs 属性时,更改会反映在 table 中。但是对 autoGroupColumnDef 属性的更改不会呈现,即使调试日志显示已检测到更改。这是 TypeScript 中的一个示例(使用 React 钩子来表示状态):

import React, { FC, useState } from 'react';
import 'ag-grid-enterprise';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-balham.css';

const AgGridTest: FC = () => {
  const rowData = [
    { col1: 'a', col2: 0, col3: 0 },
    { col1: 'b', col2: 1, col3: 1 },
    { col1: 'c', col2: 2, col3: 0 },
    { col1: 'd', col2: 3, col3: 1 },
    { col1: 'a', col2: 4, col3: 0 },
    { col1: 'b', col2: 5, col3: 1 },
    { col1: 'c', col2: 6, col3: 0 },
    { col1: 'd', col2: 7, col3: 1 },
  ];
  const [columnDefs, setColumnDefs] = useState([
    { headerName: 'Column 1', field: 'col1', rowGroup: true }, // initial group column
    { headerName: 'Column 2', field: 'col2', rowGroup: false },
    { headerName: 'Column 3', field: 'col3', rowGroup: false },
  ]);

  const [autoGroupColumnDef, setAutoGroupColumnDef] = useState(
    {headerName: 'col1 Initial'} // auto group column name is 'col1 Initial' to start
  );

  const groupByColumn = (field: string): void => {
    // this successfully changes the grouping column...
    setColumnDefs(
      prevColumnDefs => prevColumnDefs.map(
        colDef => colDef.field === field ? 
          {...colDef, rowGroup: true} :
          {...colDef, rowGroup: false}
      )
    );
    // ...but this won't change the auto group column name!
    setAutoGroupColumnDef({
      headerName: `${field} Group Column`,
    });
  }

  return (
    <div className="ag-theme-balham" style={{ height: '300px' }}>
      <button onClick={() => groupByColumn('col1')}>Group by Column 1</button>
      <button onClick={() => groupByColumn('col2')}>Group by Column 2</button>
      <AgGridReact 
        rowData={rowData}
        columnDefs={columnDefs}
        autoGroupColumnDef={autoGroupColumnDef}
        debug // enable debug logs
      />
    </div>
  );
}

const App: React.FC = () => <AgGridTest />

export default App;

我尝试调用网格 API 的 api.refreshHeader 但这也不起作用:

const AgGridTest: FC = () => {
  // ...

  // store the api in an instance variable
  // when the on ready event makes it available
  const api = useRef<GridApi>();
  const onGridReady = (params: GridReadyEvent) => {
    api.current = params.api;
  }

  const groupByColumn = (field: string): void => {
    // ... re-assign the grouping cols ...
  }

  useEffect(() => {
    // run this after grouping changes have been rendered
    gridApi.current && gridApi.current.refreshHeader(); // doesn't work
  });

  return (
    <div>
      <AgGridReact
        onGridReady={onGridReady}
        // ...
      />
    </div>
  );
}

我试图寻找一个 API 函数来设置 autoColumnGroupDef,但找不到。 documentation only mentions setting properties through gridOptions. The API has a function for updating regular column definitions: api.setColumnDefs. This is what AgGridReact calls when the columnDefs prop changes (relevant code snippets here and here). But when the autoGroupColumnDef property is changed, AgGridReact simply overwrites the autoGroupColumnDef property in the gridOptions object (relevant code snippet)。似乎没有任何关联的 setter 函数。

似乎甚至没有办法获取自动分组列对象。 columnApi.getAllColumns/getColumnGroup only return regular columns. The auto group column list is kept separately in a private instance variable in columnController (relevant code snippet),它有一个 public getPrimaryAndSecondaryAndAutoColumns 访问器,除了我们不能访问 columnController API.

关于如何在运行时修改自动组列名称的任何想法?或者我必须禁用它们然后 create my own group columns?

有人问了这个问题 here and here 但他们从未收到答案(我不明白第二个问题中的评论)。

你可能知道,ag-grid 是纯 js,而 React 和 Angular 版本是它的包装器。

我已经使用 Angular 版本大约一年了,并且对其进行了大量定制,我发现许多 gridOptions,即使它们是绑定属性,在初始后也没有效果网格的实例化。我怀疑 autoGroupColumnDef 是其中之一。

因此,我会尝试根据您引用的 link 创建您自己的列组。

如果这不起作用,(丑陋的)替代方法是销毁网格,然后使用反映新 autoGroupColumnDef 的新 gridOptions 重新创建它

另一条来之不易的建议: 如果您使用 setColumnDefs API 来更新列定义,它工作正常,但是,如果您以后想要保存和恢复网格状态,从 API 获取网格状态会产生带有“_1 " 附加到它们,如果你用它设置列状态,你会收到有关未找到列的错误。解决方案是先将 columnDefs 设置为空数组,然后再设置您实际的 columnDefs。

使用 setColumnDefs API 改变了列名。这让我陷入困境,这可能对其他人有用。

tl;博士

如果您想在运行时通过 API(例如 api.setColumnDefs)而不是仅通过 ag-Grid 的 UI 控件(例如,允许用户通过单击和拖动来调整列的大小),您必须:

  1. 为每一列指定唯一的 colId
  2. gridOptions.deltaColumnMode 设置为真。
  3. 如果您还想允许用户修改这些属性(例如单击并拖动调整大小),请添加一个显式处理程序来监视这些更改并相应地更新 columnDef。

我现在将解释原因(有些部分我仍然不清楚)。

如果您提供 colIds 但将 deltaColumnMode 设置为 false...

我制作了一个 plunkr,您可以在其中创建新列并更新新列和现有列的排序顺序、宽度以及它是否为行分组列。首先要注意的是,第 1 列的 columnDefs 中的初始宽度已应用,但对现有列或新列单击 "increase width" 不会执行任何操作。分组也不起作用 - 单元格渲染器将更改,使内容居中,但实际上不会执行任何分组。

这是因为 deltaColumnMode 默认情况下为 false,并且 当 deltaColumnMode 为 false 时,将忽略运行时对某些属性(包括宽度和行分组)的更改From the docs:

By default when new columns are loaded into the grid, the following properties are not used:

  • Column Order
  • Aggregation Function (colDef.aggFunc)
  • Width (colDef.width)
  • Pivot (colDef.pivot or colDef.pivotIndex)
  • Row Group (colDef.rowGroup or colDef.rowGroupIndex)
  • Pinned (colDef.pinned)

This is done on purpose to avoid unexpected behaviour for the application user.

For example - suppose the application user rearranges the order of the columns. Then if the application sets new column definitions for the purposes of adding one extra column into the grid, then it would be a bad user experience to reset the order of all the columns.

类似地,当我们创建新列时,我们将它放在 columnDefs 列表的第一位,但是这个顺序被忽略,而是在最后创建。这是因为列顺序是 deltaColumnMode 为 false 时忽略的属性之一。

第二件要注意的事情是 即使我们更新 columnDefs 也会保留排序顺序。因为我们提供了 colId,ag-Grid 知道当我们更改 columnDef 时,我们是在更新现有列而不是创建新列。当 colId 相同时,ag-Grid 会保留内部状态,例如排序顺序和行分组。 From the docs:

Comparison of column definitions is done on 1) object reference comparison and 2) column ID eg colDef.colId. If either the object reference matches, or the column ID matches, then the grid treats the columns as the same column. For example if the grid has a column with ID 'country' and the user sets new columns, one of which also has ID of 'country', then the old country column is kept in place of the new one keeping it's internal state such as width, position, sort and filter.

如果您不提供任何 colId 并将 deltaColumnMode 设置为 false...

如果您不在初始 columnDefs 中提供 colId,ag-Grid will assign them internally:

If the user provides colId in the column definition, then this is used, otherwise the field is used. If both colId and field then colId gets preference. If neither colId or field then numeric is provided. Then finally the ID ensured to be unique by appending '_[n]' where n is the first positive number that allows uniqueness.

每当 columnDefs 发生变化时,ag-Grid 都会通过检查 colId(或列对象引用)来决定更改是针对现有列还​​是针对新列。 From the docs:

If you are updating the columns (not replacing the entire set) then you must either provide column ID's or reuse the column definition object instances. Otherwise the grid will not know that the columns are in fact the same columns.

当我们提供 colId 时,ag-Grid 知道我们正在更新现有的列。但是当我们不提供 colIds 时,ag-Grid 不知道该怎么做。让我们看看与之前相同的 plunkr,但删除了 colId。现在,当我们单击按钮增加第 1 列的宽度时,会发生三件事:1) 第 1 列的宽度确实增加了,但是 2) 它的顺序发生了变化,所以它现在是最右边的列,以及 3) 如果我们继续单击 "increase width" ,我们在控制台中看到,第 1 列的 colId 最初未定义,在第一次增加后变为 "col1"(字段名称),然后在每隔一次增加时在 "col1_1" 和 "col1" 之间交替。

我怀疑这种奇怪的行为是由于 ag-Grid 在未提供 colId 时解析 columnDefs 更新的方式造成的。 因为没有提供 colId,当我们更新第 1 列的宽度时,ag-Grid 似乎认为我们正在创建一个新列。由于 ag-Grid 认为这是一个新列,宽度实际上更改已应用,即使 deltaColumnMode 为 false(请记住,在前面设置了 colIds 的示例中,宽度 属性 是在创建列时应用的,而不是在运行时应用的)。但是因为 deltaColumnMode 是 false,当我们创建一个新列时,它的顺序将被忽略——因此,第 1 列被放置在最右边的列。 (虽然,由于 the docs say width/column 顺序应该被忽略 "when new columns are loaded into the grid",我不太清楚为什么应用初始宽度但列顺序不是)。

当我们按第 1 列分组时会发生类似的事情,但是 1) 所有列的所有 colId 都会发生变化,以及 2) 分组列移至最左边的列。我怀疑第一个是因为在应用分组时所有列都发生了变化,第二个是因为默认情况下分组列向左移动,因为当我们创建一个新列并按它分组时,它会向左移动,也是。

您可能会注意到,有时在对第 1 列或新列进行更改后,您无法对它们应用任何排序。还记得 ag-Grid 如何在向 colId 附加和删除“_1”之间交替吗?由于 api.setSortModel 按 colId 查找列,每次 colId 附加“_1”时,api.setSortModel 将无法找到要应用排序的列(显然会无声地失败)。我们可以通过使用 columnApi.getAllColumns() 获取当前的 colId 并使用它们来解决这个问题。

此外,由于未提供 colId,ag-Grid 不会在 columnDef 更改之间保留列状态(例如排序顺序)。如果您对列进行排序,然后增加其宽度或按其分组,排序将被重置。这是因为排序是依附于列对象的内部状态,但是列对象在columnDefs改变时被销毁。

如果您提供 colIds 并将 deltaColumnMode 设置为 true...

这是一个 plunkr to demonstrate. Now, things work as we expect: runtime changes are applied, the new column is created in the leftmost position, and sort order is preserved between columnDef updates. However, there's a reason that deltaColumnMode is false by default. Again, from the docs(强调我的):

This is done on purpose to avoid unexpected behaviour for the application user.

For example - suppose the application user rearranges the order of the columns. Then if the application sets new column definitions for the purposes of adding one extra column into the grid, then it would be a bad user experience to reset the order of all the columns.

Likewise if the user changes an aggregation function, or the width of a column, or whether a column was pinned, all of these changes the user does should not get undone because the application decided to update the column definitions.

To change this behaviour and have column attributes above (order, width, row group etc) take effect each time the application updates the grid columns, then set gridOption.deltaColumnMode=true. The responsibility is then on your application to make sure the provided column definitions are in sync with what is in the grid if you don't want undesired visible changes - eg if the user changes the width of a column, the application should listen to the grid event columnWidthChanged and update the applications column definition with the new width - otherwise the width will reset back to the default after the application updates the column definitions into the grid.

尝试将第 1 列拖动到不同的顺序,然后增加其宽度。正如文档所说,您会看到它的顺序在 columnDefs 中被重置为其原始顺序。要保留列顺序,您需要为列拖动事件设置侦听器并使用新顺序更新 columnDefs - 请参阅此 plunkr 和 onDragStopped 处理程序的示例。

如果您希望允许用户通过 ag-Grid 默认 UI 控件更改聚合函数、宽度、旋转、行分组或固定,则需要为这些更改添加侦听器,也是。

我一直在处理这个问题,我终于找到了一个解决方案(虽然有点开箱即用,但它有效)

如果您只想更改 header,您可以使用 dom 操作。您只需要获取包含您希望更改的标题的确切元素。就我而言,我想按照选择的顺序显示所选聚合的列表,因此我能够通过以下方式完成它:

 aggregateMyColumn() {
    this.gridColumnApi.setRowGroupColumns(this.orderedAggregationFields);
    if(this.orderedAggregationFields.length > 0) {
      const container = document.querySelector("#agGrid");
      const matches = container.querySelector("div[col-id='ag-Grid-AutoColumn']");
      const final = container.querySelector("span[role='columnheader']");
      this.renderer.setProperty(final, 'innerHTML', this.orderedAggregationFields.toString());
    }
 }

注意:使用 renderer2 设置 properties/change innerhtml 是有原因的(有些安全),不过这里有一些有用的链接:

https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll https://www.ninjadevcorner.com/2019/04/dom-manipulation-using-angular-renderer2.html