2025, Oct 20 23:00

Make AG Grid cell flashing work in NiceGUI: update values in place with getRowId and setDataValue

Learn why AG Grid cell change flashing fails in NiceGUI and fix it by avoiding full redraws, using stable row IDs, enableCellChangeFlash, and setDataValue.

Flashing cell updates in AG Grid are a small but useful UX detail: as values change, the user’s eye is naturally pulled to what’s different. When wiring AG Grid inside NiceGUI with a Pandas DataFrame backend, though, it’s easy to set all the right options and still never see the animation trigger. The core friction is how the grid is refreshed.

Reproducing the issue

The following example renders a grid with Name and Score, turns on cell-change flashing, and attempts to programmatically highlight changed cells after mutating data. It also overrides the default green to red for visibility.

from nicegui import ui
import pandas as pd, random

# A) Register HighlightChangesModule
ui.add_head_html('''
<script>
  const { ModuleRegistry, HighlightChangesModule } = window.agGrid;
  ModuleRegistry.registerModules([ HighlightChangesModule ]);
</script>
''')

# B) Force red flash color under the Alpine theme
ui.add_head_html('''
<style>
  .ag-theme-alpine .ag-cell-data-changed {
    background-color: red !important;
    color: white !important;
    transition: background-color 0.5s ease;
  }
</style>
''')

# C) Sample data
dataset = pd.DataFrame({'id':[0,1,2], 'name': ['Alice','Bob','Charlie'], 'score': [10,20,30]})
prior = dataset.copy()

# D) Grid with change flash enabled
with ui.column().classes('w-full ag-theme-alpine'):
    table = ui.aggrid(
        {
            "columnDefs": [{"field": "id"}, {"field": "name"}, {"field": "score", 'enableCellChangeFlash': True}],
            "rowData": dataset.to_dict("records"),
            "animateRows": True,
            "cellFlashDelay": 500,
            "cellFadeDelay": 1000,
            'deltaRowDataMode': True,
            # ':getRowId': 'params => params.data.id',
        }
    )

# E) Update scores: rewrite data, refresh grid, then try to flash cells
def bump_scores():
    global dataset, prior
    prior = dataset.copy()
    dataset['score'] = [random.randint(0,100) for _ in dataset.index]

    table.options['rowData'] = dataset.to_dict('records')
    table.update()

    changed_cells = []
    for i in dataset.index:
        if dataset.at[i,'score'] != prior.at[i,'score']:
            changed_cells.append({'rowIndex': i, 'column': 'score'})
    table.run_method('api.flashCells', {'cells': changed_cells})

ui.button('Update Scores', on_click=bump_scores)
ui.run(host="127.0.0.1")

What actually goes wrong

The grid refresh is the culprit. When the entire grid is re-rendered, the internal “this value just changed” state that AG Grid uses to apply its temporary highlight class is not present on the fresh DOM. In other words, table.update() triggers a full redraw, so the grid no longer recognizes the previous value versus the new value for a given cell, and the flashing class doesn’t get applied.

Separately, if you chased a broken documentation URL while debugging, that won’t help either. And if something still refuses to work, popping open the browser’s DevTools Console to check for JavaScript errors is a practical sanity check.

The workaround that makes flashing reliable

Instead of re-rendering the grid, update cell values in place. Give AG Grid stable row identities via getRowId, enable enableCellChangeFlash, and use the grid API to set the changed cell values. This allows AG Grid to detect the delta and apply its built-in animation. The color can be customized through CSS variables to keep the flash red and text white.

from nicegui import ui
import pandas as pd
import random

records = pd.DataFrame({
    'id': [0, 1, 2],
    'name': ['Alice', 'Bob', 'Charlie'],
    'score': [10, 20, 30],
})

ui.add_head_html('''
<style>
  :root {
    --ag-value-change-value-highlight-bg-color: red;
    --ag-value-change-value-highlight-color: white;
  }
  .ag-theme-alpine .ag-cell-data-changed,
  .ag-cell-data-changed-animation {
    background-color: var(--ag-value-change-value-highlight-bg-color) !important;
    color: var(--ag-value-change-value-highlight-color) !important;
  }
</style>
''')

with ui.column().classes('w-full ag-theme-alpine'):
    sheet = ui.aggrid.from_pandas(records, options={
        'columnDefs': [
            {'headerName': col.capitalize(), 'field': col}
            for col in records.columns
        ],
        'cellFlashDuration': 800,
        'cellFadeDuration': 1500,
        'defaultColDef': {'enableCellChangeFlash': True},
        ':getRowId': 'params => params.data.id.toString()',
    })

def apply_new_scores():
    for _, rec in records.iterrows():
        nid = rec['id']
        new_val = random.randint(0, 100)
        records.loc[records['id'] == nid, 'score'] = new_val
        sheet.run_row_method(str(nid), 'setDataValue', 'score', new_val)

ui.button('Update Scores', on_click=apply_new_scores)
ui.run(host='127.0.0.1')

Why this matters

When you preserve the grid instance and only patch the changed values, AG Grid can do what it is designed to: detect differences and animate them. This avoids fighting the component lifecycle and removes the need to simulate flashing by manually hunting changed cells after a full redraw.

Takeaways

If cell-change animations are required, don’t refresh the entire grid. Keep row IDs stable, enable the built-in flashing, and call setDataValue through the NiceGUI bridge so AG Grid tracks per-cell changes. If you suspect other issues, quickly verify that the docs link you’re using is correct and check the browser console for errors. With these adjustments, the Score column flashes reliably and the UX stays focused on what changed.

The article is based on a question from StackOverflow by Iain MacCormick and an answer by Detlef.