@GreyBeardedGeek mentioned that using the setColumnDefs API mutates the column names. This sent me down a rabbit hole which may prove useful to others.
tl;dr
If you want to change column order, width, sort state, filtering, row grouping, or pivoting at runtime through the API (e.g. api.setColumnDefs) instead of only through ag-Grid's UI controls (e.g. allowing the user to resize columns by clicking and dragging), you must:
- Specify a unique
colId
for each column.
- Set
gridOptions.deltaColumnMode
to true.
- If you want to also allow the user to modify these properties (e.g. click-n-drag resize), add an explicit handler that watches for these changes and updates the columnDefs accordingly.
I'll now explain why (sort of - some parts are still unclear to me).
If you provide colIds but leave deltaColumnMode false...
I made a plunkr where you can create a new column and update the new column and an existing column's sort order, width, and whether it's a row grouping column. The first thing to note is that the initial width in the columnDefs for column 1 is applied, but clicking "increase width" for either the existing or new column does nothing. Grouping doesn't work either - the cell renderer will change, centering the content, but no grouping will actually be performed.
This is because deltaColumnMode
is false by default, and when deltaColumnMode is false, runtime changes to certain properties (which include width and row grouping) are ignored. 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.
Similarly, when we create the new column, we place it first in the list of columnDefs, but this order is ignored and it's instead created at the end. This is because column order is one of the properties ignored when deltaColumnMode is false.
The second thing to note is that sort order is preserved even when we make columnDefs updates. Because we provided colIds, ag-Grid knew that when we changed the columnDefs, we were updating existing columns and not creating new ones. ag-Grid preserves internal state like sort order and row grouping when the colIds are the same. 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.
If you don't provide any colIds and leave deltaColumnMode false...
If you don't provide colIds in the initial columnDefs, 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.
Whenever columnDefs change, ag-Grid decides whether the changes are to an existing column or to a new column by checking the colIds (or column object references). 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.
When we provided colIds, ag-Grid knew we were updating existing columns. But when we don't provide colIds, ag-Grid doesn't know what to do. Let's look at the same plunkr as before but with colIds removed. Now, when we click the button to increase column 1's width, three things happen: 1) column 1's width does increase, but 2) its order shifts so it's now the rightmost column, and 3) if we keep clicking "increase width", we see in the console that column 1's colId was initially undefined, becomes "col1" (the field name) after the first increase, and then alternates between "col1_1" and "col1" on every other increase.
I suspect that this strange behavior is due to the way ag-Grid resolves columnDefs updates when colIds aren't provided. Because no colId is provided, ag-Grid seems to think that we're creating a new column when we update column 1's width. Since ag-Grid thinks this is a new column, the width change is actually applied, even though deltaColumnMode is false (remember that in the previous example with colIds set, the width property was applied when the column was created, just not during runtime). But because deltaColumnMode is false, when we create a new column, its order is ignored - hence, column 1 is placed as the rightmost column. (Though, since the docs say that width/column order should be ignored "when new columns are loaded into the grid", I don't quite know why the initial width is applied but column order is not).
Similar things happen when we group by column 1, but 1) all colIds change for all columns, and 2) the grouping column is moved to be the leftmost column. I suspect the first is because all columns change when grouping is applied, and the second is because the grouping column is moved to the left by default, as when we create a new column and group by it, it'll be moved to the left, too.
You may notice that sometimes after making changes to either column 1 or the new column, you're not able to apply any sorting to them. Remember how ag-Grid alternates between appending and removing a "_1" to the colIds? Since api.setSortModel looks up columns by colIds, every time the colId has a "_1" appended to it, api.setSortModel won't be able to find the column to apply sorting (and will fail silently, apparently). We could fix this by getting the current colIds with columnApi.getAllColumns() and using those instead.
Also, since no colIds are provided, ag-Grid doesn't preserve column state (e.g. sort order) between columnDef changes. If you sort a column and then increase its width or group by it, the sorting will be reset. This is because sorting is attached to the internal state of the column object, but the column objects are destroyed when the columnDefs change.
If you provide colIds and set deltaColumnMode to true...
Here's a 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 (emphasis mine):
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.
Try dragging column 1 to a different order and then increasing its width. You'll see that its order gets reset to its original order in the columnDefs, just as the docs say. To preserve column order, you need to set a listener for the column drag event and update the columnDefs with the new order - see this plunkr and the onDragStopped handler for an example.
And if you want to allow the user to change aggregation functions, width, pivoting, row grouping, or pinning through the ag-Grid default UI controls, you'll need to add listeners for these changes, too.