Migrating Apex Grid v3 to v4
Apex Grid 4.0 is a major release. Most apps upgrade with no code change, but a few defaults and types moved. This guide lists what to check when upgrading from 3.6, then what is new. apex-grid-enterprise 0.9 ships alongside it and needs it (see Enterprise 0.9).
At a glance
| Area | v3 | v4 |
|---|---|---|
| Body rows | Record-keyed: each row element stays with its record | Recycled by default (rowRecycling); opt out with rowRecycling = false |
| Grid role | role, aria-rowcount, aria-colcount on the <apex-grid> host | On the inner [part~="table"] element |
| Ignite UI theme | --ig-primary-* and --ig-font-family tinted the grid with no import | Needs import 'apex-grid/ignite-ui' |
--ag-font | Root default on :root | No root default |
| Pagination | Pinned rows counted toward totalItems and the pages | Pinned rows are left out of the page count |
DataPipelineConfiguration.pagination | Typed, never called | Removed |
min() / max() | Text that is not a number passed | Text that is not a number fails |
Install
npm install apex-grid@^4.0.0
# with the enterprise package
npm install apex-grid@^4.0.0 apex-grid-enterprise@^0.9.0
apex-grid-enterprise 0.9 requires apex-grid@^4.0.0, and its modules fail to load against a 3.x grid, so upgrade both together. Its apexcharts, lit and @lit/context peer ranges are unchanged.
Body rows are recycled by default
A scroll, sort or filter now re-binds the rendered row elements to the records in view instead of building new rows, which makes long jumps and sorts cheaper on large grids. A row element and its cells can therefore show another record later.
What you might notice:
- A
cellTemplateoreditorTemplatethat keeps state in the elements it creates (an input's typed value, a third-party widget) can show that state on another record after a scroll or a sort. - Code that holds
grid.rowselements, orctx.row/ctx.parent, across a scroll, a sort or anawaitfinds them showing other records.
Templates should derive what they render from their context on every render, and read the record from the new ctx.data:
import { html } from 'lit';
import { keyed } from 'lit/directives/keyed.js';
{
key: 'notes',
// ctx.data is the record this cell was rendered for, and stays that record after an await
cellTemplate: (ctx) => html`<span title=${ctx.data.owner}>${ctx.value}</span>`,
}
{
key: 'widget',
// a template that keeps element state is rebuilt per record
cellTemplate: (ctx) => keyed(ctx.data, html`<my-widget .value=${ctx.value}></my-widget>`),
}
To keep record-keyed rows, turn recycling off:
grid.rowRecycling = false;
<apex-grid row-recycling="false"></apex-grid>
Detail panel content follows its record and keeps its DOM state across a scroll, a sort or a moveRow, so detail templates need no change. See Cell Template.
The grid role moved to the table part
role (grid or treegrid), aria-rowcount, aria-colcount, aria-multiselectable and the accessible name are now on an inner element exposed as part table. It holds only the header rows, the pinned rows and the body, so the toolbar, paginator, filter panel and live region are no longer read as table content. The layout is unchanged.
Code or tests that read these attributes from the host should query the part instead:
const table = grid.shadowRoot.querySelector('[part~="table"]');
table.getAttribute('aria-rowcount');
An aria-label or aria-labelledby on the host still names the grid, and aria-describedby becomes its description. See Accessibility.
Ignite UI theming is opt-in
In 3.x the grid took the brand colour (--ig-primary-500, --ig-primary-700) and the font (--ig-font-family) from an Ignite UI theme on the page by itself. In 4.0 an Ignite UI stylesheet changes nothing until you import the add-on, which brings the brand and font back along with the rest of the theme:
import 'apex-grid/define';
import 'apex-grid/ignite-ui';
Two related changes:
--ag-fonthas no root default any more, so an explicit--ag-fontwins over the family and Ignite UI fonts. Page CSS that readsvar(--ag-font)needs its own fallback, as with--ag-brand. The default Inter stack is unchanged.- The grid follows the family
--apx-*tokens (--apx-accent,--apx-fore,--apx-grid,--apx-surface,--apx-font-family,--apx-radius-factor). A page that sets them on:rootfor its charts sees the grid follow them too. Set the matching--ag-*tokens to keep the grid as it was, or set the family tokens on the elements they are meant for instead of:root.tokens="off"on a grid stops the accent, font and radius family tokens, but--apx-fore,--apx-gridand--apx-surfaceset on:rootstill reach its text, line and surface defaults unless theapex-grid/ignite-uiadd-on is loaded.
See Using with Ignite UI.
Pagination
- Pinned rows no longer count toward the pages.
totalItems,pageCount, the paginator summary and thepageChangeddetail count the rows the pager pages through. Without paginationtotalItemsstill counts every row. - A local page or page size change no longer reruns the pipeline, so custom sort and filter hooks in
dataPipelineConfigurationare not called on a local page change. Remote mode still reruns it. DataPipelineConfiguration.paginationand the'pagination'value ofDataPipelineParams.typeare removed. The hook was never called, so no runtime behaviour changes, but TypeScript code that sets it stops compiling. For server paging usepagination.mode: 'remote', fetch the page onpageChangedand keeppagination.totalItemscurrent. See Remote pagination.pageChangedreports the change that was applied, so two quicknextPage()calls report page 2 and then page 3.- A page change is announced once ("Page X of N"); the paginator summary and page label are no longer live regions.
Validators
min() and max() fail a value that is not a number. Before, 'abc' (or any text Number() could not read) passed both. Typed text is now read the way a person writes it, so 123, 1,000 or 1.234,5 in a column with locale: 'de-DE' still pass as numbers. Empty values still pass; compose with required().
Smaller type and DOM changes
ctx.datais a required field ofBaseApexCellContext, so code that builds a cell context by hand must supply it.ApexGridPaginator.pageSizeOptionsis typedreadonly number[].- The paginator's previous button has the id
prev(it wasprevious) inside its shadow root. Target it withapex-grid::part(paginator-prev)instead. - The protected
ApexGrid.headerRowCountandApexGridHeaderRow.renderDragGhostare removed, which affects subclasses only. pageItemskeeps the same array between updates while nothing that shapes it changes. Code that compared it by identity sees a new array only when the body rows change.
Other changes you may notice
- The column header row is no longer a Tab stop.
- The built-in column menu opens in the top layer where the browser supports the Popover API, so it can extend outside the grid's box.
- The column drag ghost renders at the grid root, and
apex-grid::part(drag-ghost)reaches it. - The paginator follows
--header-background, and its text size follows--ag-fs-cell-secondary. - The grid uses the
treegridrole whenever rows carry levels or expand: tree data, master-detail with a detail template, and the enterprise row grouping, expandable pivot and server-side grouping. - The first grid on a page adds one window
errorlistener that cancels "ResizeObserver loop" messages and hides them from listeners added after it. Listeners registered before the first grid still receive them, and every other error is untouched.
New in 4.0
showHeader(show-header="false") leaves out the header rows and the filter panel, for a short grid such as a dashboard tile. See Column Configuration.selection.isRowSelectable(row)keeps chosen rows, such as a pinned total row, out of selection. See Row Selection.pagination.paginator: 'compact'draws the small paginator. See Pagination.apex-grid::part()reaches every paginator and toolbar part. See Pagination and Toolbar.ctx.datain cell and editor templates.- The
apex-grid/ignite-uiadd-on and the family--apx-*tokens. - Input method (IME) support in the cell editor, the filter panel and the quick filter, and numbers typed in full width or with the column locale's grouping. See Data Filtering.
- Locale keys for text that stayed English, and
ValidatorContext.localize. See Localization.
Enterprise 0.9
apex-grid-enterprise 0.9 takes everything above, and apps may also notice:
- Body rows are recycled in
<apex-grid-enterprise>too. Master-detail detail grids take the master grid'srowRecyclingwhen they are created, so a master that turns it off turns it off for its detail grids.configureDetailruns after and can set its own. - A bulk write fires one
apex-view-changed(applyEdits, a range paste, a fill drag, or undo or redo of a batch) instead of one per cell. After a single cell edit the event fires when the grid finishes its update, a microtask later, rather than inside thecellValueChangeddispatch. - Only the visible columns' values reach a model or proxy. Row fields that are not columns, and hidden columns, are no longer sent.
maxDataRowsoncreateClaudeReasoneralso bounds the proxy transport. - AI requests and replies are checked more strictly. Without a
mode, only "how many", "number of", "what is", "what are" or a trailing "?" make a prompt a question; passmode: 'ask'for the rest. In a proxy response,answermust be a string andpatchan object; any other shape rejects with an error. - Charts skip blank and non-numeric cells when they aggregate, so avg, min, max, median and count match the grid's own group totals. The forecast band is a true 95% prediction interval, and thousands, percent and currency labels format for the locale.
ChartSeries.datais typedreadonly (number | null)[], and the tool panel'sgriddefaults tonulllike the other companion elements.- The filter builder's value field is a text input for numeric columns too, so it reads the column's
localeand no longer has a spinner.
New in 0.9: the popups and the chart panel follow the grid's theme (and an Ignite UI theme with apex-grid/ignite-ui), IME input and full-width formulas and numbers, more locale keys, and the ApexGridEnterpriseHost, ApexGridEnterpriseEventMap, ParseError, FormulaAst and FormulaExportOptions exports.