---
title: "Client-Side Data - High Frequency Updates"
framework: javascript
version: "36.1.0"
---

# Client-Side Data - High Frequency Updates

High Frequency Updates relates to lots of updates in high succession going into the grid. Every time you update data in the grid, the grid will rework all aggregations, sorts and filters as well as having the browser update its DOM. If you are streaming multiple updates into the grid this can be a bottleneck. High Frequency Updates are achieved in the grid using Async Transactions. Async Transactions allow for efficient high-frequency grid updates.

## Async Transactions

When you call `applyTransactionAsync()` the grid will execute the update, along with any other updates you subsequently provide using `applyTransactionAsync()`, after 50ms. This allows the grid to execute all the transactions in one batch which is more efficient.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `applyTransactionAsync` | `Function` |  |  | Same as `applyTransaction` except executes asynchronously for efficiency. Module: [`ClientSideRowModelApiModule`](https://www.ag-grid.com/javascript-data-grid/modules/). |

The following example demonstrates updating data using normal transactions and async transactions:

- **Normal Update**: Calls `applyTransaction()` 5000 times with each call updating a single row.
- **Async Update**: Calls `applyTransactionAsync()` 5000 times with each call updating a single row.

#### Async Transaction

```ts
import {
  CellStyleModule,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  GetRowIdParams,
  GridApi,
  GridOptions,
  HighlightChangesModule,
  ModuleRegistry,
  ValueFormatterParams,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule, RowGroupingPanelModule } from "ag-grid-enterprise";
import { getData, globalRowData } from "./data";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelApiModule,
  CellStyleModule,
  ClientSideRowModelModule,
  RowGroupingModule,
  RowGroupingPanelModule,
  HighlightChangesModule,
]);

const UPDATE_COUNT = 5000;

const columnDefs: ColDef[] = [
  // these are the row groups, so they are all hidden (they are show in the group column)
  {
    headerName: "Product",
    field: "product",
    enableRowGroup: true,
    rowGroupIndex: 0,
    hide: true,
  },
  {
    headerName: "Portfolio",
    field: "portfolio",
    enableRowGroup: true,
    rowGroupIndex: 1,
    hide: true,
  },
  {
    headerName: "Book",
    field: "book",
    enableRowGroup: true,
    rowGroupIndex: 2,
    hide: true,
  },
  { headerName: "Trade", field: "trade", width: 100 },

  // all the other columns (visible and not grouped)
  {
    field: "current",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    field: "previous",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    field: "dealType",
    enableRowGroup: true,
  },
  {
    headerName: "Bid",
    field: "bidFlag",
    enableRowGroup: true,
    width: 100,
  },
  {
    headerName: "PL 1",
    field: "pl1",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "PL 2",
    field: "pl2",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "Gain-DX",
    field: "gainDx",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "SX / PX",
    field: "sxPx",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "99 Out",
    field: "_99Out",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    field: "submitterID",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    field: "submitterDealID",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
];

function numberCellFormatter(params: ValueFormatterParams) {
  return Math.floor(params.value)
    .toString()
    .replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
}

let gridApi: GridApi;
const gridOptions: GridOptions = {
  columnDefs: columnDefs,
  suppressAggFuncInHeader: true,
  rowGroupPanelShow: "always",
  getRowId: (params: GetRowIdParams) => String(params.data.trade),
  defaultColDef: {
    width: 120,
  },
  autoGroupColumnDef: {
    width: 250,
  },
  onGridReady: (params) => {
    getData();
    params.api.setGridOption("rowData", globalRowData);
  },
};

// picks a row at random and returns an updated copy: the old current value
// becomes the previous value, and a new random current value is generated.
// the updated row is also written back to globalRowData, so the next update
// to that row starts from the latest values.
function createRandomUpdate() {
  const index = Math.floor(window.agRandom() * globalRowData.length);
  const item = globalRowData[index];
  const updatedItem = {
    ...item,
    previous: item.current,
    current: Math.floor(window.agRandom() * 100000) + 100,
  };
  globalRowData[index] = updatedItem;
  return updatedItem;
}

function setMessage(msg: string) {
  const eMessage = document.querySelector("#eMessage")!;
  eMessage.textContent = msg;
}

function onNormalUpdate() {
  const startMillis = new Date().getTime();

  setMessage("Running Transaction");

  for (let i = 0; i < UPDATE_COUNT; i++) {
    setTimeout(() => {
      // do normal update. update is done before method returns
      gridApi.applyTransaction({ update: [createRandomUpdate()] });
    }, 0);
  }

  // print message in next VM turn to allow browser to refresh first.
  // we assume the browser executes the timeouts in order they are created,
  // so this timeout executes after all the update timeouts created above.
  setTimeout(() => {
    const duration = new Date().getTime() - startMillis;
    setMessage("Transaction took " + duration.toLocaleString() + "ms");
  }, 0);
}

function onAsyncUpdate() {
  const startMillis = new Date().getTime();

  setMessage("Running Async");

  let updatedCount = 0;
  for (let i = 0; i < UPDATE_COUNT; i++) {
    setTimeout(() => {
      // update using async method. passing the callback is
      // optional, we are doing it here so we know when the update
      // was processed by the grid.
      gridApi.applyTransactionAsync(
        { update: [createRandomUpdate()] },
        resultCallback,
      );
    }, 0);
  }

  function resultCallback() {
    updatedCount++;
    if (updatedCount === UPDATE_COUNT) {
      // print message in next VM turn to allow browser to refresh
      setTimeout(() => {
        const duration = new Date().getTime() - startMillis;
        setMessage("Async took " + duration.toLocaleString() + "ms");
      }, 0);
    }
  }
}

// after page is loaded, create the grid.
const eGridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(eGridDiv, gridOptions);

if (typeof window !== "undefined") {
  // Attach external event handlers to window so they can be called from index.html
  (<any>window).onNormalUpdate = onNormalUpdate;
  (<any>window).onAsyncUpdate = onAsyncUpdate;
}
```

[Live example: Async Transaction](https://www.ag-grid.com/examples/data-update-high-frequency/async-transaction/typescript)

To help understand the interface for `applyTransaction()` and `applyTransactionAsync()`, here are both method signatures side by side. The first executes immediately. The second executes sometime later using a callback for providing a result.

```ts
// normal applyTransaction takes a RowDataTransaction and returns a RowNodeTransaction
applyTransaction(rowDataTransaction: RowDataTransaction): RowNodeTransaction

// batch takes a RowDataTransaction and the result is provided some time later via a callback
applyTransactionAsync(
    rowDataTransaction: RowDataTransaction,
    callback?: (res: RowNodeTransaction) => void
): void
```

Use Async Transactions if you have a high volume of streaming data going into the grid and don't want the grid's rendering and recalculating to be a bottleneck.

## Async Transactions Flushed Event

Each time the grid executes a batch of Async Transactions, it dispatches an `asyncTransactionsFlushed` event.

The event contains `results` attribute, which is a list of all the results for all Transactions that got applied.

This event is useful for debugging or observing how the Async Transactions are applied for learning purposes.

## Flush Async Transactions

The default wait between executing batches is 50ms. This means when an Async Transaction is provided to the grid, it can take up to 50ms for that transaction to be applied.

Sometimes you may want all transactions to be applied before doing something - for example you may want to select a row in the grid but want to make sure the grid has all the latest row data before doing so.

To make sure the grid has no Async Transactions pending, you can flush the Async Transaction queue. This is done by calling the API `flushAsyncTransactions`.

It is also possible to change the wait between executing batches from the default 50ms. This is done using the grid property `asyncTransactionWaitMillis`.

The example below demonstrates setting the wait time and also flushing. Note the following:

- The property `asyncTransactionWaitMillis` is set to 4000, thus transactions get flushed every 4 seconds.
- The button Flush Transactions will call the API method `flushAsyncTransactions`.
- Transactions getting added and executed is logged to the console.
- The example listens on event `asyncTransactionsFlushed` and logs how many transactions got applied.

#### Flush Transactions

```ts
import {
  AsyncTransactionsFlushedEvent,
  CellStyleModule,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  GetRowIdParams,
  GridApi,
  GridOptions,
  HighlightChangesModule,
  ModuleRegistry,
  ValueFormatterParams,
  createGrid,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule, RowGroupingPanelModule } from "ag-grid-enterprise";
import { getData, globalRowData } from "./data";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelApiModule,
  CellStyleModule,
  ClientSideRowModelModule,
  RowGroupingModule,
  RowGroupingPanelModule,
  HighlightChangesModule,
]);

const UPDATE_COUNT = 20;

const columnDefs: ColDef[] = [
  // these are the row groups, so they are all hidden (they are show in the group column)
  {
    headerName: "Product",
    field: "product",
    enableRowGroup: true,
    rowGroupIndex: 0,
    hide: true,
  },
  {
    headerName: "Portfolio",
    field: "portfolio",
    enableRowGroup: true,
    rowGroupIndex: 1,
    hide: true,
  },
  {
    headerName: "Book",
    field: "book",
    enableRowGroup: true,
    rowGroupIndex: 2,
    hide: true,
  },
  { headerName: "Trade", field: "trade", width: 100 },

  // all the other columns (visible and not grouped)
  {
    headerName: "Current",
    field: "current",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "Previous",
    field: "previous",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "Deal Type",
    field: "dealType",
    enableRowGroup: true,
  },
  {
    headerName: "Bid",
    field: "bidFlag",
    enableRowGroup: true,
    width: 100,
  },
  {
    headerName: "PL 1",
    field: "pl1",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "PL 2",
    field: "pl2",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "Gain-DX",
    field: "gainDx",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "SX / PX",
    field: "sxPx",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "99 Out",
    field: "_99Out",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "Submitter ID",
    field: "submitterID",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
  {
    headerName: "Submitted Deal ID",
    field: "submitterDealID",
    width: 200,
    aggFunc: "sum",
    enableValue: true,
    cellClass: "number",
    valueFormatter: numberCellFormatter,
    cellRenderer: "agAnimateShowChangeCellRenderer",
  },
];

function numberCellFormatter(params: ValueFormatterParams) {
  return Math.floor(params.value)
    .toString()
    .replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
}

let gridApi: GridApi;

const gridOptions: GridOptions = {
  columnDefs: columnDefs,
  suppressAggFuncInHeader: true,
  rowGroupPanelShow: "always",
  asyncTransactionWaitMillis: 4000,
  getRowId: (params: GetRowIdParams) => String(params.data.trade),
  defaultColDef: {
    width: 120,
  },
  autoGroupColumnDef: {
    width: 250,
  },
  onGridReady: (params) => {
    getData();
    params.api.setGridOption("rowData", globalRowData);
    startFeed(params.api);
  },
  onAsyncTransactionsFlushed: (e: AsyncTransactionsFlushedEvent) => {
    console.log(
      "========== onAsyncTransactionsFlushed: applied " +
        e.results.length +
        " transactions",
    );
  },
};

function onFlushTransactions() {
  gridApi!.flushAsyncTransactions();
}

function startFeed(api: GridApi) {
  let count = 1;

  setInterval(() => {
    const thisCount = count++;
    const updatedIndexes = new Set<number>();
    const updatedItems: any[] = [];
    for (let i = 0; i < UPDATE_COUNT; i++) {
      // pick one row at random, skipping rows already updated in this transaction
      const index = Math.floor(window.agRandom() * globalRowData.length);
      if (updatedIndexes.has(index)) {
        continue;
      }
      updatedIndexes.add(index);

      // the old current value becomes the previous value
      const item = globalRowData[index];
      const updatedItem = {
        ...item,
        previous: item.current,
        current: Math.floor(window.agRandom() * 100000) + 100,
      };

      // write back, so the next update to this row starts from the latest values
      globalRowData[index] = updatedItem;
      updatedItems.push(updatedItem);
    }
    api.applyTransactionAsync({ update: updatedItems }, () => {
      console.log("transactionApplied() - " + thisCount);
    });
    console.log("applyTransactionAsync() - " + thisCount);
  }, 500);
}

// after page is loaded, create the grid.
const eGridDiv = document.querySelector<HTMLElement>("#myGrid")!;
gridApi = createGrid(eGridDiv, gridOptions);

if (typeof window !== "undefined") {
  // Attach external event handlers to window so they can be called from index.html
  (<any>window).onFlushTransactions = onFlushTransactions;
}
```

[Live example: Flush Transactions](https://www.ag-grid.com/examples/data-update-high-frequency/flush-transactions/typescript)
