---
title: "Client-Side Data - Transaction Updates"
framework: angular
version: "36.1.0"
---

# Client-Side Data - Transaction Updates

Transaction Updates allow large numbers of rows in the grid to be added, removed or updated in an efficient manner. Use Transaction Updates for fast changes to large datasets.

## Transaction Update API

A transaction object contains the details of what rows should be added, removed and updated. The grid API `applyTransaction(transaction)` takes this transaction object and applies it to the grid's data.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `applyTransaction` | `Function` |  |  | Update row data. Pass a transaction object with lists for `add`, `remove` and `update`. Module: [`ClientSideRowModelApiModule`](https://www.ag-grid.com/angular-data-grid/modules/). |

The result of the `applyTransaction(transaction)` is also a transaction, however it is a list of [Row Nodes](https://www.ag-grid.com/angular-data-grid/row-object/) that were added, removed or updated. Both types of transactions look similar, but the difference is the data type they contain.

- **Row Data Transaction**: Contains Row Data, the data that you are providing to the grid.
- **Row Node Transaction**: Contains Row Nodes, the grid-created objects that wrap row data items.

For each data item in a Row Data Transaction there is typically a Row Node in Row Node Transaction wrapping that data item. The only exception is for edge cases, for example you tried to delete or update a data item that didn't exist.

## Example: Updating with Transaction

The example applies transactions in different ways and prints the results of the call to the console. The following can be noted:

- **Add Items**: Adds three items.
- **Add Items addIndex=2**: Adds items at index 2.
- **Update Top 2**: Updates the price on the first 2 rows in the list.
- **Remove Selected**: Removes all the selected rows from the list.
- **Get Row Data**: Prints all row data in the grid to the console.
- **Clear Data**: Sets the data in the grid to an empty list.

#### Updating with Transaction

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowApiModule,
  RowNodeTransaction,
  RowSelectionModule,
  RowSelectionOptions,
  enableDevValidations,
} from "ag-grid-community";
import { getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelApiModule,
  RowSelectionModule,
  RowApiModule,
  ClientSideRowModelModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div style="height: 100%; display: flex; flex-direction: column">
    <div style="margin-bottom: 4px">
      <button (click)="addItems(undefined)">Add Items</button>
      <button (click)="addItems(2)">Add Items addIndex=2</button>
      <button (click)="updateItems()">Update Top 2</button>
      <button (click)="onRemoveSelected()">Remove Selected</button>
      <button (click)="getRowData()">Get Row Data</button>
      <button (click)="clearData()">Clear Data</button>
    </div>
    <div style="flex-grow: 1">
      <ag-grid-angular
        style="width: 100%; height: 100%;"
        [columnDefs]="columnDefs"
        [defaultColDef]="defaultColDef"
        [rowData]="rowData"
        [rowSelection]="rowSelection"
        (gridReady)="onGridReady($event)"
      />
    </div>
  </div> `,
})
export class AppComponent {
  private gridApi!: GridApi;

  columnDefs: ColDef[] = [
    { field: "make" },
    { field: "model" },
    { field: "price" },
    { field: "zombies" },
    { field: "style" },
    { field: "clothes" },
  ];
  defaultColDef: ColDef = {
    flex: 1,
  };
  rowData: any[] | null = getData();
  rowSelection: RowSelectionOptions | "single" | "multiple" = {
    mode: "multiRow",
  };

  getRowData() {
    const rowData: any[] = [];
    this.gridApi.forEachNode(function (node) {
      rowData.push(node.data);
    });
    console.log("Row Data:");
    console.table(rowData);
  }

  clearData() {
    const rowData: any[] = [];
    this.gridApi.forEachNode(function (node) {
      rowData.push(node.data);
    });
    const res = this.gridApi.applyTransaction({
      remove: rowData,
    })!;
    printResult(res);
  }

  addItems(addIndex: number | undefined) {
    const newItems = [
      createNewRowData(),
      createNewRowData(),
      createNewRowData(),
    ];
    const res = this.gridApi.applyTransaction({
      add: newItems,
      addIndex: addIndex,
    })!;
    printResult(res);
  }

  updateItems() {
    // update the first 2 items
    const itemsToUpdate: any[] = [];
    this.gridApi.forEachNodeAfterFilterAndSort(function (rowNode, index) {
      // only do first 2
      if (index >= 2) {
        return;
      }
      const data = rowNode.data;
      data.price = Math.floor(window.agRandom() * 20000 + 20000);
      itemsToUpdate.push(data);
    });
    const res = this.gridApi.applyTransaction({ update: itemsToUpdate })!;
    printResult(res);
  }

  onRemoveSelected() {
    const selectedData = this.gridApi.getSelectedRows();
    const res = this.gridApi.applyTransaction({ remove: selectedData })!;
    printResult(res);
  }

  onGridReady(params: GridReadyEvent) {
    this.gridApi = params.api;
  }
}

let newCount = 1;
function createNewRowData() {
  const newData = {
    make: "Toyota " + newCount,
    model: "Celica " + newCount,
    price: 35000 + newCount * 17,
    zombies: "Headless",
    style: "Little",
    clothes: "Airbag",
  };
  newCount++;
  return newData;
}
function printResult(res: RowNodeTransaction) {
  console.log("---------------------------------------");
  if (res.add) {
    res.add.forEach((rowNode) => {
      console.log("Added Row Node", rowNode);
    });
  }
  if (res.remove) {
    res.remove.forEach((rowNode) => {
      console.log("Removed Row Node", rowNode);
    });
  }
  if (res.update) {
    res.update.forEach((rowNode) => {
      console.log("Updated Row Node", rowNode);
    });
  }
}
```

[Live example: Updating with Transaction](https://www.ag-grid.com/examples/data-update-transactions/updating-with-transaction/angular)

## Identifying Rows for Update and Remove

When passing in data to be updated or removed, the grid is asking:

*"What row do you mean exactly by this data item you are passing?"*

There are two approaches you can take: 1) Providing Row IDs, or 2) Using Object References.

- ### Providing Row IDs (Faster)

  If you are providing [Row IDs](https://www.ag-grid.com/angular-data-grid/row-ids/) using the grid callback `getRowId()` then the grid matches data provided in the transaction with data in the grid using the key.

  For updating rows, the grid finds the row with the same key and then swap the data out for the newly provided data.

  For removing rows, the grid finds the row with the same key and remove it. For this reason, the provided records within the `remove` array only need to have a key present.

```ts
<ag-grid-angular
    [getRowId]="getRowId"
    /* other grid options ... */ />

this.getRowId = (params) => params.data.employeeId;
```

```js
const myTransaction = {
    add: [
        // adding a row, there should be no row with ID = 4 already
        {employeeId: '4', name: 'Billy', age: 55}
    ],
    update: [
        // updating a row, the grid looks for the row with ID = 2 to update
        {employeeId: '2', name: 'Bob', age: 23}
    ],
    remove: [
        // deleting a row, only the ID is needed, other attributes (name, age) don't serve any purpose
        {employeeId: '5'}
    ]
}
```

- ### Using Object References (Slower)

  If you do not provide [Row IDs](https://www.ag-grid.com/angular-data-grid/row-ids/) for the rows, the grid compares rows using object references. In other words when you provide a transaction with update or remove items, the grid does an array lookup to find those rows using the `===` operator on the data that you previously provided.

  When using object references, note the following:
  1. The same instance of the row data items should be used. Using another instance of the same object stops the grid from making the comparison.
  2. Using object references for identification is slow for large data sets, as the grid has no way of indexing rows based on object reference.

Although using object references is slower, this is only an issue if you are working with large datasets (thousands of rows).

## Example: Updating with Transaction and Groups

When using transactions and grouping, the groups are kept intact as you add, remove and update rows. The example below demonstrates the following:

- **Add For Sale:** Adds a new item to 'For Sale' group.
- **Add In Workshop:** Adds a new item to 'In Workshop' group.
- **Remove Selected:** Removes all selected items.
- **Move to For Sale:** Move selected items to 'For Sale' group.
- **Move to In Workshop:** Move selected items to 'In Workshop' group.
- **Move to Sold:** Move selected items to 'Sold' group.
- When moving items, the grid animates the rows to the new location with minimal DOM updates.
- **Get Row Data:** Prints all row data to the console.

#### Updating with Transaction and Groups

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
  AutoGroupColumnDef,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowClass,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowApiModule,
  RowClassParams,
  RowSelectionModule,
  RowSelectionOptions,
  RowStyleModule,
  ValueFormatterParams,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule } from "ag-grid-enterprise";
import { createNewRowData, getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelApiModule,
  RowSelectionModule,
  RowApiModule,
  RowStyleModule,
  ClientSideRowModelModule,
  RowGroupingModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="example-wrapper">
    <div style="margin-bottom: 5px">
      <div>
        <button class="bt-action" (click)="onAddRow('For Sale')">
          Add For Sale
        </button>
        <button class="bt-action" (click)="onAddRow('In Workshop')">
          Add In Workshop
        </button>
        <button class="bt-action" (click)="onRemoveSelected()">
          Remove Selected
        </button>
        <button class="bt-action" (click)="getRowData()">Get Row Data</button>
      </div>
      <div style="margin-top: 5px">
        <button class="bt-action" (click)="onMoveToGroup('For Sale')">
          Move to For Sale
        </button>
        <button class="bt-action" (click)="onMoveToGroup('In Workshop')">
          Move to In Workshop
        </button>
        <button class="bt-action" (click)="onMoveToGroup('Sold')">
          Move to Sold
        </button>
      </div>
    </div>

    <ag-grid-angular
      style="width: 100%; height: 100%;"
      [columnDefs]="columnDefs"
      [defaultColDef]="defaultColDef"
      [autoGroupColumnDef]="autoGroupColumnDef"
      [groupDefaultExpanded]="groupDefaultExpanded"
      [rowData]="rowData"
      [rowSelection]="rowSelection"
      [suppressAggFuncInHeader]="true"
      [getRowClass]="getRowClass"
      (gridReady)="onGridReady($event)"
    />
  </div> `,
})
export class AppComponent {
  private gridApi!: GridApi;

  columnDefs: ColDef[] = [
    { field: "category", rowGroupIndex: 1, hide: true },
    { field: "price", aggFunc: "sum", valueFormatter: poundFormatter },
    { field: "zombies" },
    { field: "style" },
    { field: "clothes" },
  ];
  defaultColDef: ColDef = {
    flex: 1,
    width: 100,
  };
  autoGroupColumnDef: AutoGroupColumnDef = {
    headerName: "Group",
    minWidth: 250,
    field: "model",
    rowGroupIndex: 1,
    cellRenderer: "agGroupCellRenderer",
  };
  groupDefaultExpanded = 1;
  rowData: any[] | null = getData();
  rowSelection: RowSelectionOptions | "single" | "multiple" = {
    mode: "multiRow",
    groupSelects: "descendants",
    headerCheckbox: false,
    checkboxLocation: "autoGroupColumn",
  };
  getRowClass: GetRowClass = (params: RowClassParams) => {
    const rowNode = params.node;
    if (rowNode.group) {
      switch (rowNode.key) {
        case "In Workshop":
          return "category-in-workshop";
        case "Sold":
          return "category-sold";
        case "For Sale":
          return "category-for-sale";
        default:
          return undefined;
      }
    } else {
      // no extra classes for leaf rows
      return undefined;
    }
  };

  getRowData() {
    const rowData: any[] = [];
    this.gridApi.forEachNode(function (node) {
      rowData.push(node.data);
    });
    console.log("Row Data:");
    console.log(rowData);
  }

  onAddRow(category: string) {
    const rowDataItem = createNewRowData(category);
    this.gridApi.applyTransaction({ add: [rowDataItem] });
  }

  onMoveToGroup(category: string) {
    const selectedRowData = this.gridApi.getSelectedRows();
    selectedRowData.forEach((dataItem) => {
      dataItem.category = category;
    });
    this.gridApi.applyTransaction({ update: selectedRowData });
  }

  onRemoveSelected() {
    const selectedRowData = this.gridApi.getSelectedRows();
    this.gridApi.applyTransaction({ remove: selectedRowData });
  }

  onGridReady(params: GridReadyEvent) {
    this.gridApi = params.api;
  }
}

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

[Live example: Updating with Transaction and Groups](https://www.ag-grid.com/examples/data-update-transactions/updating-with-transaction-and-groups/angular)

## Localised Changes in Grouped Data

When you apply a transaction to grouped data, the grid only re-applies grouping, filtering and sorting to the impacted data.

For example, suppose you have the grid with its rows grouped into 10 groups and a sort is applied on one column. If a transaction is applied to update one row, then the group that row sits within is re-sorted as well as the top level group (as aggregations could impact values at the top level). All the other 9 groups do not need to have their sorting re-applied.

Deciding what groups need to be operated on within the grid is called Changed Path Selection. After the grid applies all adds, removes and updates from a transaction, it works out what groups were impacted and only executes the required operations on those groups. The groups that were impacted include each group with data that was changed, as well as all parents of changed groups all the way up to the top level.

The example below demonstrates Changed Path Selection. The example is best viewed with the dev console open so log messages can be observed. Note the following:

- The 'Distro' column is sorted with a custom comparator. The comparator records how many times it is called.
- The Value column is aggregated with a custom aggregator. The aggregator records how many times it is called.
- When the example first loads, all the data is set into the grid which results in 171 aggregation operations (one for each group), approximately 24,000 comparisons (for sorting all rows in each group, the number of sorts differ slightly depending on the data values which are random in this example) and 10,000 filter passes (one for each row). The number of milliseconds to complete the operation is also printed (this value depends on your hardware).
- Select a row and click **Update**, **Delete** OR **Duplicate** (duplicate results in an add operation). Note in the console that the number of aggregations, compares and filters is drastically fewer. The total time to execute is also drastically less.

#### Small Changes Big Data

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
  AutoGroupColumnDef,
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  CustomFilterModule,
  DoesFilterPassParams,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HighlightChangesModule,
  IAggFuncParams,
  IsGroupOpenByDefault,
  IsGroupOpenByDefaultParams,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  RowSelectionOptions,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule } from "ag-grid-enterprise";
import { createDataItem, getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelApiModule,
  NumberFilterModule,
  RowSelectionModule,
  HighlightChangesModule,
  ClientSideRowModelModule,
  RowGroupingModule,
  CustomFilterModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="test-container">
    <div class="test-header">
      <button (click)="onBtUpdate()">Update</button>
      <button (click)="onBtDuplicate()">Duplicate</button>
      <button (click)="onBtDelete()">Delete</button>
      <button (click)="onBtClearSelection()">Clear Selection</button>
    </div>
    <ag-grid-angular
      style="width: 100%; height: 100%;"
      class="test-grid"
      [getRowId]="getRowId"
      [columnDefs]="columnDefs"
      [defaultColDef]="defaultColDef"
      [rowSelection]="rowSelection"
      [autoGroupColumnDef]="autoGroupColumnDef"
      [enableFilterHandlers]="true"
      [isGroupOpenByDefault]="isGroupOpenByDefault"
      [rowData]="rowData"
      (gridReady)="onGridReady($event)"
    />
  </div> `,
})
export class AppComponent {
  private gridApi!: GridApi;

  columnDefs: ColDef[] = [
    { field: "city", rowGroup: true, hide: true },
    { field: "laptop", rowGroup: true, hide: true },
    { field: "distro", sort: "asc", comparator: myComparator },
    {
      field: "value",
      enableCellChangeFlash: true,
      aggFunc: myAggFunc,
      filter: {
        component: "agNumberColumnFilter",
        doesFilterPass: ({
          model,
          node,
          handlerParams,
        }: DoesFilterPassParams) => {
          filterCallCount++;
          return model == null || handlerParams.getValue(node) > model.filter;
        },
      },
      filterParams: {
        filterOptions: ["greaterThan"],
        maxNumConditions: 1,
      },
    },
  ];
  defaultColDef: ColDef = {
    flex: 1,
    filter: true,
  };
  rowSelection: RowSelectionOptions | "single" | "multiple" = {
    mode: "multiRow",
    groupSelects: "descendants",
    headerCheckbox: false,
  };
  autoGroupColumnDef: AutoGroupColumnDef = {
    field: "name",
  };
  rowData!: any[];

  onBtDuplicate() {
    // get the first child of the
    const selectedRows = this.gridApi.getSelectedRows();
    if (!selectedRows || selectedRows.length === 0) {
      console.log("No rows selected!");
      return;
    }
    const newItems: any = [];
    selectedRows.forEach((selectedRow) => {
      const newItem = createDataItem(
        selectedRow.name,
        selectedRow.distro,
        selectedRow.laptop,
        selectedRow.city,
        selectedRow.value,
      );
      newItems.push(newItem);
    });
    timeOperation("Duplicate", () => {
      this.gridApi.applyTransaction({ add: newItems });
    });
  }

  onBtUpdate() {
    // get the first child of the
    const selectedRows = this.gridApi.getSelectedRows();
    if (!selectedRows || selectedRows.length === 0) {
      console.log("No rows selected!");
      return;
    }
    const updatedItems: any[] = [];
    selectedRows.forEach((oldItem) => {
      const newValue = Math.floor(window.agRandom() * 100) + 10;
      const newItem = createDataItem(
        oldItem.name,
        oldItem.distro,
        oldItem.laptop,
        oldItem.city,
        newValue,
        oldItem.id,
      );
      updatedItems.push(newItem);
    });
    timeOperation("Update", () => {
      this.gridApi.applyTransaction({ update: updatedItems });
    });
  }

  onBtDelete() {
    // get the first child of the
    const selectedRows = this.gridApi.getSelectedRows();
    if (!selectedRows || selectedRows.length === 0) {
      console.log("No rows selected!");
      return;
    }
    timeOperation("Delete", () => {
      this.gridApi.applyTransaction({ remove: selectedRows });
    });
  }

  onBtClearSelection() {
    this.gridApi.deselectAll();
  }

  onGridReady(params: GridReadyEvent) {
    this.gridApi = params.api;

    params.api.setFilterModel({
      value: { filterType: "number", type: "greaterThan", filter: 50 },
    });
    timeOperation("Initialisation", () => {
      params.api.setGridOption("rowData", getData());
    });
  }

  getRowId = (params: GetRowIdParams) => {
    return String(params.data.id);
  };

  isGroupOpenByDefault = (
    params: IsGroupOpenByDefaultParams<IOlympicData, any>,
  ) => {
    return ["Delhi", "Seoul"].includes(params.key);
  };
}

let aggCallCount = 0;
let compareCallCount = 0;
let filterCallCount = 0;
function myAggFunc(params: IAggFuncParams) {
  aggCallCount++;
  let total = 0;
  for (let i = 0; i < params.values.length; i++) {
    total += params.values[i];
  }
  return total;
}
function myComparator(a: any, b: any) {
  compareCallCount++;
  return a < b ? -1 : 1;
}
function timeOperation(name: string, operation: any) {
  aggCallCount = 0;
  compareCallCount = 0;
  filterCallCount = 0;
  const start = new Date().getTime();
  operation();
  const end = new Date().getTime();
  console.log(
    name +
      " finished in " +
      (end - start) +
      "ms, aggCallCount = " +
      aggCallCount +
      ", compareCallCount = " +
      compareCallCount +
      ", filterCallCount = " +
      filterCallCount,
  );
}
```

[Live example: Small Changes Big Data](https://www.ag-grid.com/examples/data-update-transactions/small-changes-big-data/angular)

> **Note**
>
> Note that [Header Checkbox Selection](https://www.ag-grid.com/angular-data-grid/row-selection-multi-row/#selecting-all-rows) is not turned on for the example above. If it was it would slow the grid down marginally as it requires each row to be checked (for selection state) between each update. If you need a blazing fast grid managing rapid changes, consider avoiding this feature.

## Suppress Model Updates

To perform update transactions and prevent the grid from automatically reprocessing grouping, filters, aggregation and sorting, enable the `suppressModelUpdateAfterUpdateTransaction` grid option. This can be helpful to prevent data moving while the user is in an edit state for a row.

Note that this property is only used when transactions are applied that only have updates. If the transaction contains any adds or removes, the sorting, filtering and grouping is always applied.

To trigger the Client-side Row Model to refresh, use the Grid API `refreshClientSideRowModel(startingStage)`. As the stages are sequential, the Client-side Row Model refreshes at the provided stage and all the stages after. The ordering of these stages is: group, filter, pivot, aggregate, filter aggregates, sort, and map.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `refreshClientSideRowModel` | `Function` |  |  | Refresh the Client-Side Row Model, executing the grouping, filtering and sorting again. Optionally provide the step you wish the refresh to apply from. Defaults to `everything`. Module: [`ClientSideRowModelApiModule`](https://www.ag-grid.com/angular-data-grid/modules/). |

#### Suppress Update Model

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GridApi,
  GridOptions,
  GridReadyEvent,
  HighlightChangesModule,
  ModuleRegistry,
  NumberFilterModule,
  RowApiModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule, SetFilterModule } from "ag-grid-enterprise";
import { createDataItem, getData } from "./data";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  ClientSideRowModelApiModule,
  TextFilterModule,
  SetFilterModule,
  RowApiModule,
  HighlightChangesModule,
  ClientSideRowModelModule,
  RowGroupingModule,
  NumberFilterModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="test-container">
    <div class="test-header">
      <button (click)="onBtnApply()">Apply Transaction</button>
      <button (click)="onBtnRefreshModel()">Refresh Model</button>
    </div>
    <ag-grid-angular
      style="width: 100%; height: 100%;"
      class="test-grid"
      [getRowId]="getRowId"
      [columnDefs]="columnDefs"
      [defaultColDef]="defaultColDef"
      [suppressModelUpdateAfterUpdateTransaction]="true"
      [rowData]="rowData"
      (gridReady)="onGridReady($event)"
    />
  </div> `,
})
export class AppComponent {
  private gridApi!: GridApi;

  columnDefs: ColDef[] = [
    { field: "name" },
    { field: "laptop" },
    {
      field: "fixed",
      enableCellChangeFlash: true,
    },
    {
      field: "value",
      enableCellChangeFlash: true,
      sort: "desc",
    },
  ];
  defaultColDef: ColDef = {
    flex: 1,
    filter: true,
    floatingFilter: true,
  };
  rowData!: any[];

  onBtnApply() {
    const updatedItems: any[] = [];
    this.gridApi.forEachNode((rowNode) => {
      const newValue = Math.floor(window.agRandom() * 100) + 10;
      const newBoolean = Boolean(Math.round(window.agRandom()));
      const newItem = createDataItem(
        rowNode.data.name,
        rowNode.data.laptop,
        newBoolean,
        newValue,
        rowNode.data.id,
      );
      updatedItems.push(newItem);
    });
    this.gridApi.applyTransaction({ update: updatedItems });
  }

  onBtnRefreshModel() {
    this.gridApi.refreshClientSideRowModel("filter");
  }

  onGridReady(params: GridReadyEvent) {
    this.gridApi = params.api;

    params.api
      .setColumnFilterModel("fixed", {
        filterType: "set",
        values: ["true"],
      })
      .then(() => {
        params.api.onFilterChanged();
      });
    params.api.setGridOption("rowData", getData());
  }

  getRowId = (params) => {
    return String(params.data.id);
  };
}
```

[Live example: Suppress Update Model](https://www.ag-grid.com/examples/data-update-transactions/suppress-update-model/angular)

In the example above `suppressModelUpdateAfterUpdateTransaction=true`. Note the following:

1. When data is updated, using the **Apply Transaction** button, the grid does not re-execute sorting or filtering.
2. After data is updated, hitting **Update Model** triggers the grid to sort and filter using `refreshClientSideRowModel('filter')`.

## Delta Sorting

While using transactions to modify the grids data model, the default behaviour when a row is modified is for the grid to sort the modified row as well as every sibling into a new ordered list.

The `deltaSort` option is a performance enhancement which may (depending upon your configuration) provide a faster experience by instead:

- Filtering deleted and updated rows from the previously sorted data set
- Only sorting the changed and added rows together in a new list
- Merging the cleaned and changes lists to produce a new sorted data set

It is possible that, if using transactions, enabling this feature may provide a performance boost to your application. Delta sort may be beneficial to you if:

- Your data has a large amount of rows in each row group (or a large number of rows with no grouping) and comparatively small transactions
- You're sorting by a large number of columns simultaneously

Some caveats also exist which may make delta sorting slower in your application if:

- Your data set is small compared to the size of your transactions
- You have a large number of groups containing small numbers of rows.

Delta sort is ignored (full sort runs instead) when [`postSortRows`](https://www.ag-grid.com/angular-data-grid/row-sorting/#post-sort) is configured.

In the below example two buttons have been provided, one using delta sort and one without. When clicked they use a transaction to insert one row and update one existing row on the initial dataset of 100,000 rows.

#### Delta Sorting

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import "./styles.css";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetRowIdFunc,
  GetRowIdParams,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  enableDevValidations,
} from "ag-grid-community";
import { RowGroupingModule } from "ag-grid-enterprise";
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

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

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="test-container">
    <div class="test-header">
      <div>
        <button (click)="addDefault()">Default Transaction</button>
        <button (click)="addDelta()">Delta Transaction</button>
        Transaction took: <span id="transactionDuration">N/A</span>
      </div>
    </div>
    <ag-grid-angular
      style="width: 100%; height: 100%;"
      class="test-grid"
      [columnDefs]="columnDefs"
      [defaultColDef]="defaultColDef"
      [rowData]="rowData"
      [deltaSort]="true"
      [getRowId]="getRowId"
      (gridReady)="onGridReady($event)"
    />
  </div> `,
})
export class AppComponent {
  private gridApi!: GridApi;

  columnDefs: ColDef[] = [
    { field: "id" },
    { field: "updatedBy" },
    { field: "sort", sortIndex: 0, sort: "desc" },
    { field: "sort1", sortIndex: 1, sort: "desc" },
    { field: "sort2", sortIndex: 2, sort: "desc" },
  ];
  defaultColDef: ColDef = {
    flex: 1,
  };
  rowData: any[] | null = getRowData(100000);
  getRowId: GetRowIdFunc = ({ data }: GetRowIdParams) => String(data.id);

  addDelta() {
    const transaction = {
      add: getRowData(1).map((row) => ({ ...row, updatedBy: "delta" })),
      update: [{ id: 1, make: "Delta", updatedBy: "delta" }],
    };
    this.gridApi.setGridOption("deltaSort", true);
    const startTime = new Date().getTime();
    this.gridApi.applyTransaction(transaction);
    document.getElementById("transactionDuration")!.textContent =
      `${new Date().getTime() - startTime} ms`;
  }

  addDefault() {
    const transaction = {
      add: getRowData(1).map((row) => ({ ...row, updatedBy: "default" })),
      update: [{ id: 2, make: "Default", updatedBy: "default" }],
    };
    this.gridApi.setGridOption("deltaSort", false);
    const startTime = new Date().getTime();
    this.gridApi.applyTransaction(transaction);
    document.getElementById("transactionDuration")!.textContent =
      `${new Date().getTime() - startTime} ms`;
  }

  onGridReady(params: GridReadyEvent) {
    this.gridApi = params.api;
  }
}

let lastGen = 0;
const generateItem = (id = lastGen++) => {
  return {
    id,
    sort: Math.floor(window.agRandom() * 3 + 2000),
    sort1: Math.floor(window.agRandom() * 3 + 2000),
    sort2: Math.floor(window.agRandom() * 100000 + 2000),
  };
};
const getRowData = (rows = 10) =>
  new Array(rows).fill(undefined).map((_) => generateItem());
```

[Live example: Delta Sorting](https://www.ag-grid.com/examples/data-update-transactions/delta-sorting/angular)
