---
product: "AG Grid"
title: "Set Filter - Data Updates"
description: "This section describes how changing data through and the application impacts the Set Filter's values. This is only applicable when the Set Filter is taking its values from the grid's data, except for , which also covers provided values."
enterprise: true
framework: angular
version: "36.3.0"
related:
    - title: "Filter List"
      url: "https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set-filter-list/"
    - title: "Tree List"
      url: "https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set-tree-list/"
    - title: "Mini Filter"
      url: "https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set-mini-filter/"
    - title: "Excel Mode"
      url: "https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set-excel-mode/"
    - title: "API"
      url: "https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set-api/"
llms: "https://www.ag-grid.com/archive/36.3.0/llms.txt"
---

# Set Filter - Data Updates

This section describes how changing data through [Cell Editing](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/cell-editing/) and the application [Updating Data](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/data-update/) impacts the Set Filter's values. This is only applicable when the Set Filter is taking its values from the grid's data, except for [Preserving Previous Values](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set-data-updates/#preserving-previous-values), which also covers provided values.

## Row / Cell Updates

Row / Cell updates refers to any of the following:

- All edits via the grid's UI such as [Cell Editing](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/cell-editing/) and [Clipboard Operations](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/clipboard/).
- Using the grid's [Single Row / Cell Update API](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/data-update-single-row-cell/) i.e. `rowNode.setData(data)` and `rowNode.setDataValue(col,value)` API methods.

Filter Values will be refreshed when data is updated through any of these methods.

Here are the rules that determine how Filter Values are selected:

- **Filter Inactive**: Before the update 'all' values in the filter were selected (as the filter was inactive). The filter list will be updated reflecting the data change and all values will remain selected leaving the filter inactive
- **Filter Active**: Before the update 'some' values in the filter were selected (as the filter was active). The filter list will be updated reflecting the data change, however previous selections will remain intact as long as the filter value still exists after any transaction(s). If the update resulted in a new filter value, the new filter value will not be selected.

Cell editing does not re-execute filtering by default, so the row will not be filtered out even though the value in the cell is not selected in the filter. This default behaviour mimics how Excel works.

To execute filtering on cell edits, listen to `CellValueChanged` events and trigger filtering as shown below:

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `cellValueChanged` | `CellValueChangedEvent` |  |  | Cell value has changed. This occurs after the following scenarios: - Editing. Will not fire if any of the following are true: new value is the same as old value; `readOnlyEdit = true`; editing was cancelled (e.g. Escape key was pressed); or new value is of the wrong cell data type for the column. - Cut. - Paste. - Cell clear (pressing Delete key). - Fill handle. - Copy range down. - Undo and redo. |

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

this.onCellValueChanged = params => {
    // trigger filtering on cell edits
    params.api.onFilterChanged();
};
```

The following example demonstrates Cell Editing with the Set Filter. Try the following:

**Without selecting any Filter Values:**

- Change (Cell Edit) a `'B'` cell value to `'X'` and note it gets added to the filter list and is **selected**.

**Click 'Reset' and deselect 'C' in the Filter List:**

- Change (Cell Edit) a `'B'` cell value to `'X'` and notice it gets added to the Filter List but it is **not selected**.
- Note that although `'X'` is not selected in the filter the row still appears in the grid. This is because grid filtering is not triggered for cell edits.

#### Cell Editing Updates

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  SideBarDef,
  TextEditorModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  FiltersToolPanelModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import "./styles.css";

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

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  FiltersToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  SetFilterModule,
  TextEditorModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="example-wrapper">
    <div style="margin-bottom: 5px">
      <button (click)="reset()">Reset</button>
    </div>
    <ag-grid-angular
      style="width: 100%; height: 100%;"
      [rowData]="rowData"
      [columnDefs]="columnDefs"
      [sideBar]="sideBar"
      (firstDataRendered)="onFirstDataRendered($event)"
      (gridReady)="onGridReady($event)"
    />
  </div> `,
})
export class AppComponent {
  private gridApi!: GridApi;

  rowData: any[] | null = getRowData();
  columnDefs: ColDef[] = [
    {
      headerName: "Set Filter Column",
      field: "col1",
      filter: "agSetColumnFilter",
      editable: true,
      minWidth: 250,
    },
  ];
  sideBar: SideBarDef | string | string[] | boolean | null = "filters";

  onFirstDataRendered(params: FirstDataRenderedEvent) {
    params.api.getToolPanelInstance("filters")!.expandFilters();
  }

  reset() {
    this.gridApi.setFilterModel(null);
    this.gridApi.setGridOption("rowData", getRowData());
  }

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

function getRowData() {
  return [
    { col1: "A" },
    { col1: "A" },
    { col1: "B" },
    { col1: "B" },
    { col1: "C" },
    { col1: "C" },
  ];
}
```

[Live example: Cell Editing Updates](https://www.ag-grid.com/archive/36.3.0/examples/filter-set-data-updates/cell-editing-updates/angular/)

## Transaction Updates

Transaction Updates refers to any of the following:

- Updating data via [Transactions](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/data-update-transactions/) API.
- Updating data via [Async Transactions](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/data-update-high-frequency/) API.
- Changes to Row Data and providing [Row ID](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/row-ids/) (as this uses Transactions underneath the hood).

Filter values are refreshed when data is updated using any of these methods.

Here are the rules that determine how filter values are selected:

- **Filter Inactive**: Before the update 'all' values in the filter were selected (as the filter was inactive). The filter list will be updated reflecting the data change and all values will remain selected leaving the filter inactive.
- **Filter Active**: Before the update 'some' values in the filter were selected (as the filter was active). The filter list will be updated reflecting the data change, however previous selections will remain intact as long as the filter value still exists after any transaction(s). If the update resulted in a new filter value, the new filter value will not be selected.

Unlike [Cell Editing](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set-data-updates/#row--cell-updates), transaction updates will execute filtering in the grid.

The following example demonstrates these rules. Try the following:

**Without selecting any filter values:**

- Click **Update First Displayed Row**: this calls `api.applyTransaction()` and updates the value in the first row. Note `'AX'` now appears in the filter list and is **selected**.
- Click **Add New 'D' Row**: this calls `api.applyTransaction()` and adds a new row to the grid. Note `'D'` has been added to the filter list and is **selected**.

**Click 'Reset' and deselect 'C' in the Filter List:**

- Click **Update First Displayed Row**: this calls `api.applyTransaction()` and updates the value in the first row. Note `'AX'` now appears in the filter list and is **not selected**.
- Note that as `'AX'` is unselected in the filter list it has also been filtered out of the grid. This is because transaction updates also triggers grid filtering.
- Click **Add New 'D' Row**: this calls `api.applyTransaction()` and adds a new row to the grid. Note `'D'` has been added to the filter list and is **not selected**.

#### Transaction Updates

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
  ClientSideRowModelApiModule,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  RowApiModule,
  SideBarDef,
  TextEditorModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  FiltersToolPanelModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import "./styles.css";

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

ModuleRegistry.registerModules([
  ClientSideRowModelApiModule,
  RowApiModule,
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  FiltersToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  SetFilterModule,
  TextEditorModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="example-wrapper">
    <div style="margin-bottom: 5px">
      <label>Transaction Updates: </label>
      <button (click)="updateFirstRow()">Update First Displayed Row</button>
      <button (click)="addDRow()">Add New 'D' Row</button>
      <button (click)="reset()" style="margin-left: 20px">Reset</button>
    </div>

    <ag-grid-angular
      style="width: 100%; height: 100%;"
      [rowData]="rowData"
      [columnDefs]="columnDefs"
      [sideBar]="sideBar"
      (firstDataRendered)="onFirstDataRendered($event)"
      (gridReady)="onGridReady($event)"
    />
  </div> `,
})
export class AppComponent {
  private gridApi!: GridApi;

  rowData: any[] | null = getRowData();
  columnDefs: ColDef[] = [
    {
      headerName: "Set Filter Column",
      field: "col1",
      filter: "agSetColumnFilter",
      editable: true,
      minWidth: 250,
    },
  ];
  sideBar: SideBarDef | string | string[] | boolean | null = "filters";

  onFirstDataRendered(params: FirstDataRenderedEvent) {
    params.api.getToolPanelInstance("filters")!.expandFilters();
  }

  updateFirstRow() {
    const firstRow = this.gridApi.getDisplayedRowAtIndex(0);
    if (firstRow) {
      const firstRowData = firstRow.data;
      firstRowData["col1"] += "X";
      this.gridApi.applyTransaction({ update: [firstRowData] });
    }
  }

  addDRow() {
    this.gridApi.applyTransaction({ add: [{ col1: "D" }] });
  }

  reset() {
    this.gridApi.setFilterModel(null);
    this.gridApi.setGridOption("rowData", getRowData());
  }

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

function getRowData() {
  return [
    { col1: "A" },
    { col1: "A" },
    { col1: "B" },
    { col1: "B" },
    { col1: "C" },
    { col1: "C" },
  ];
}
```

[Live example: Transaction Updates](https://www.ag-grid.com/archive/36.3.0/examples/filter-set-data-updates/transaction-updates/angular/)

## Setting New Data

When `api.setGridOption('rowData', data)` is called existing filter selections are kept when new rows are added. However it is possible to clear filter selections using: `api.setFilterModel(null)`.

The following example demonstrates how `api.setGridOption('rowData', data)` affects filter selections. Try the following:

- Deselect value 'A' from the set filter list and click the **Apply Data Update 1** button which calls `api.setGridOption('rowData', newData)` to add new data with extra rows to the grid.
- Notice 'A' remains deselected after new data is supplied to the grid.
- Clicking **Reset** invokes `api.setGridOption('rowData', origData)` to restore the original data but also clears any selections using `api.setFilterModel(null)`.
- Apply the filters so only 'B' is selected and click **Apply Data Update 1**. Notice how 'B' is no longer a filter value and therefore no longer in the filter list. Click **Apply Data Update 2** and notice how 'B' returns to the filter list but remains unselected.

#### Setting New Data

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  SideBarDef,
  TextEditorModule,
  TextFilterModule,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  ColumnsToolPanelModule,
  ContextMenuModule,
  FiltersToolPanelModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import "./styles.css";

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

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ColumnsToolPanelModule,
  FiltersToolPanelModule,
  ColumnMenuModule,
  ContextMenuModule,
  SetFilterModule,
  TextFilterModule,
  TextEditorModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="example-wrapper">
    <div style="margin-bottom: 5px">
      <button (click)="updateOne()">Apply Data Update 1</button>
      <button (click)="updateTwo()">Apply Data Update 2</button>
      <button (click)="reset()" style="margin-left: 5px">Reset</button>
    </div>

    <ag-grid-angular
      style="width: 100%; height: 100%;"
      [columnDefs]="columnDefs"
      [sideBar]="sideBar"
      [rowData]="rowData"
      (firstDataRendered)="onFirstDataRendered($event)"
      (gridReady)="onGridReady($event)"
    />
  </div> `,
})
export class AppComponent {
  private gridApi!: GridApi;

  columnDefs: ColDef[] = [
    {
      headerName: "Set Filter Column",
      field: "col1",
      filter: "agSetColumnFilter",
      editable: true,
      minWidth: 250,
    },
  ];
  sideBar: SideBarDef | string | string[] | boolean | null = "filters";
  rowData: any[] | null = getRowData();

  onFirstDataRendered(params: FirstDataRenderedEvent) {
    params.api.getToolPanelInstance("filters")!.expandFilters();
  }

  updateOne() {
    const newData = [
      { col1: "A" },
      { col1: "A" },
      { col1: "C" },
      { col1: "D" },
      { col1: "E" },
    ];
    this.gridApi.setGridOption("rowData", newData);
  }

  updateTwo() {
    const newData = [
      { col1: "A" },
      { col1: "A" },
      { col1: "B" },
      { col1: "C" },
      { col1: "D" },
      { col1: "E" },
      { col1: "B" },
      { col1: "B" },
    ];
    this.gridApi.setGridOption("rowData", newData);
  }

  reset() {
    this.gridApi.setFilterModel(null);
    this.gridApi.setGridOption("rowData", getRowData());
  }

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

function getRowData() {
  return [{ col1: "A" }, { col1: "A" }, { col1: "B" }, { col1: "C" }];
}
```

[Live example: Setting New Data](https://www.ag-grid.com/archive/36.3.0/examples/filter-set-data-updates/setting-new-data/angular/)

## Preserving Previous Values

By default, a value that leaves the data is removed from the Filter List and its selected state is lost: if it later returns, it comes back as a new value. Set `preservePreviousValues` to keep values the filter has seen, together with whether they were selected. This suits filters that must survive data changing underneath them, for example:

- a saved or linked filter model applied before the rows it names have loaded;
- data that is cleared and reloaded, or replaced with `api.setGridOption('rowData', data)`;
- live updates and transactions that remove a value's rows and later add them back.

| Property | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `preservePreviousValues` | `boolean` |  | `false` | If `true`, values that leave the data (or the provided values) stay in the Filter List with their selected state, so they are filtered the same way when they return. Model values not in the data are kept too. |
| `preservePreviousValuesLimit` | `number` |  | `1000` | Requires `preservePreviousValues = true`. The most values not in the filter model to keep once they are no longer in the data; the first to leave is discarded first. Values in the filter model, or named by an applied Advanced Filter expression, are always kept and not counted. Set to `0` to keep only those, or `-1` for no limit, so memory use is unbounded. |
| `preservePreviousValuesLabel` | `string` |  |  | Requires `preservePreviousValues = true`. Text shown as given after each value in the Filter List that is no longer in the data, for example `'(not in rows)'`. |

With this enabled:

- A selected value whose rows are all removed stays selected, so its rows pass the filter again when they return.
- An unselected value whose rows are all removed stays unselected, so its rows stay filtered out when they return.
- Values no longer in the data keep their place in the list, so one leaving or returning does not move the others. Their checkbox has a dashed border, and screen readers, and the default tooltip when `showTooltips` is set, describe them as not in the current data. Set `preservePreviousValuesLabel` to show a text after them, as given, such as `'(not in rows)'`; a long value is truncated before it. They can still be selected and deselected, and take part in the Mini Filter and **(Select All)**. In a [Tree List](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set-tree-list/) a group is marked the same way once every value under it is no longer in the data.
- Values in a filter model that the data has never contained are kept and listed by their key, after the other values and marked the same way, so a model set before any rows are loaded is applied as written rather than reduced to the values present. Having no value, they are not passed to `valueFormatter`, the comparator, a cell renderer or the Tree List's `treeListPathGetter` and `treeListFormatter`, and `getFilterValues()` returns `null` for them.
- The floating filter and the Filters Tool Panel include selected values that are no longer in the data, as the filter still selects them.
- New values are added as normal: unselected while the filter is active, and selected while it is inactive.
- Values supplied through `filterParams.values` or `setFilterValues` work the same way: a value missing from the latest list is kept.
- Unless `caseSensitive` is set, a value that returns in another case keeps the case it had when it left.
- When values are taken from the grid, a value hidden only because another column's filter excludes its rows is still in the data, so it is not kept: it is hidden from the list as usual. A `values` callback that leaves out such values drops them from the provided values, so they are kept.

The following example demonstrates preserving previous values. Try the following:

- Click **Select Algeria and Argentina**, then **Show 2004**. Afghanistan, Algeria and Armenia won no medals in 2004, but they stay in their places in the **Country** list, labelled `(not in rows)`: Algeria still selected, so its rows would pass again, and Afghanistan and Armenia still unselected. Only Argentina's rows are shown, and the floating filter and the **Filter Summaries** panel still name Algeria.
- Click **Clear Preserved Values** to discard Afghanistan, Algeria and Armenia. They leave the list, and Algeria leaves the model with them.
- Click **Show 2008**. The three return as new, unselected values, so Algeria's rows stay filtered out. Click **Show 2008** straight after **Show 2004** instead, without clearing, and Algeria's rows are shown again.

#### Preserving Previous Values

```ts
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  FirstDataRenderedEvent,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  SideBarDef,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  FiltersToolPanelModule,
  NewFiltersToolPanelModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import "./styles.css";

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

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  FiltersToolPanelModule,
  NewFiltersToolPanelModule,
  ColumnMenuModule,
  SetFilterModule,
]);
import { IOlympicData } from "./interfaces";

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="example-wrapper">
    <div style="margin-bottom: 5px">
      <button (click)="selectAlgeriaAndArgentina()">
        Select Algeria and Argentina
      </button>
      <button (click)="showYear(2004)" style="margin-left: 5px">
        Show 2004
      </button>
      <button (click)="clearPreservedValues()">Clear Preserved Values</button>
      <button (click)="showYear(2008)">Show 2008</button>
      <button (click)="reset()" style="margin-left: 5px">Reset</button>
    </div>

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

  columnDefs: ColDef[] = [
    { field: "athlete", minWidth: 180 },
    {
      field: "country",
      filter: "agSetColumnFilter",
      filterParams: {
        preservePreviousValues: true,
        preservePreviousValuesLabel: "(not in rows)",
      },
      floatingFilter: true,
      minWidth: 200,
    },
    { field: "sport" },
    { field: "year" },
  ];
  defaultColDef: ColDef = {
    flex: 1,
  };
  sideBar: SideBarDef | string | string[] | boolean | null = {
    toolPanels: [
      "filters",
      {
        id: "filters-new",
        labelDefault: "Filter Summaries",
        labelKey: "filterSummaries",
        iconKey: "filter",
        toolPanel: "agNewFiltersToolPanel",
      },
    ],
    defaultToolPanel: "filters",
  };
  rowData!: IOlympicData[];

  constructor(private http: HttpClient) {}

  onFirstDataRendered(params: FirstDataRenderedEvent<IOlympicData>) {
    params.api.getToolPanelInstance("filters")!.expandFilters(["country"]);
  }

  showYear(year: number) {
    this.gridApi.setGridOption("rowData", rowsOfYear(year));
  }

  selectAlgeriaAndArgentina() {
    this.gridApi.setFilterModel({
      country: { filterType: "set", values: ["Algeria", "Argentina"] },
    });
  }

  clearPreservedValues() {
    this.gridApi.doFilterAction({
      colId: "country",
      action: "clearPreservedValues",
    });
  }

  reset() {
    this.gridApi.setFilterModel(null);
    this.gridApi.setGridOption("rowData", rowsOfYear(2008));
    this.gridApi.doFilterAction({
      colId: "country",
      action: "clearPreservedValues",
    });
  }

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

    this.http
      .get<IOlympicData[]>(
        "https://www.ag-grid.com/example-assets/olympic-winners.json",
      )
      .subscribe((data) => {
        allData = data;
        params.api!.setGridOption("rowData", rowsOfYear(2008));
      });
  }
}

let allData: IOlympicData[] = [];
function rowsOfYear(year: number) {
  return allData.filter((row) => row.year === year);
}
```

[Live example: Preserving Previous Values](https://www.ag-grid.com/archive/36.3.0/examples/filter-set-data-updates/preserve-previous-values/angular/)

The [Advanced Filter](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-advanced/) only suggests current values as you type, but still accepts a value that has left the data. Its Builder lists the values no longer in the data in their place, marked as the Filter List does. The values an applied expression names are kept as a column filter's selected values are, and the rest count towards `preservePreviousValuesLimit`. In the following example, click **Show 2012**, then type `[Country] is any of ["Austria"]` and apply it: Austria is no longer in the data and is not suggested, but it is accepted and no rows are shown. Click **Show 2008** and Austria's rows are shown again.

#### Preserving Previous Values with the Advanced Filter

```ts
import { Component } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { AgGridAngular } from "ag-grid-angular";
import {
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  ModuleRegistry,
  enableDevValidations,
} from "ag-grid-community";
import { AdvancedFilterModule, SetFilterModule } from "ag-grid-enterprise";
import "./styles.css";

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

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  SetFilterModule,
  AdvancedFilterModule,
]);
import { IOlympicData } from "./interfaces";

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="example-wrapper">
    <div style="margin-bottom: 5px">
      <button (click)="showYear(2012)">Show 2012</button>
      <button (click)="showYear(2008)">Show 2008</button>
      <button (click)="reset()" style="margin-left: 5px">Reset</button>
    </div>

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

  columnDefs: ColDef[] = [
    { field: "athlete", minWidth: 180 },
    {
      field: "country",
      filter: "agSetColumnFilter",
      filterParams: {
        preservePreviousValues: true,
      },
      minWidth: 200,
    },
    { field: "sport" },
    { field: "year" },
  ];
  defaultColDef: ColDef = {
    flex: 1,
  };
  rowData!: IOlympicData[];

  constructor(private http: HttpClient) {}

  showYear(year: number) {
    this.gridApi.setGridOption("rowData", rowsOfYear(year));
  }

  reset() {
    this.gridApi.setAdvancedFilterModel(null);
    this.gridApi.setGridOption("rowData", rowsOfYear(2008));
    this.gridApi.doFilterAction({ action: "clearPreservedValues" });
  }

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

    this.http
      .get<IOlympicData[]>(
        "https://www.ag-grid.com/example-assets/olympic-winners.json",
      )
      .subscribe((data) => {
        allData = data;
        params.api!.setGridOption("rowData", rowsOfYear(2008));
      });
  }
}

let allData: IOlympicData[] = [];
function rowsOfYear(year: number) {
  return allData.filter((row) => row.year === year);
}
```

[Live example: Preserving Previous Values with the Advanced Filter](https://www.ag-grid.com/archive/36.3.0/examples/filter-set-data-updates/preserve-previous-values-advanced-filter/angular/)

In a [Tree List](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set-tree-list/) on a group column, a value no longer in the data stays in its group, marked, and a group is marked once every value under it is gone. A model value that was never in the data is listed at the root by its key, in its own row even beside a group with the same label. The following examples show this with [Row Grouping](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/grouping/) and [Tree Data](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/tree-data/). Try the following, opening the filter from the floating filter's button after each step, with its groups expanded, to see the result:

- Click **Select Paris and Madrid** (or **Select report.pdf and old.zip**). Madrid (or old.zip) is not in the data, so it is listed at the root by its key, `'Spain#Madrid'` (or `'Archive/old.zip'`).
- Click **Remove Rome** (or **Remove beach.jpg**). The value stays in its group, marked and unselected.
- Click **Remove France** (or **Remove Documents**). The whole group is marked, and `'Paris'` (or `'report.pdf'`) stays selected.
- Click **Clear Preserved Values** to discard the values no longer in the data. `'Paris'` and `'Madrid'` (or `'report.pdf'` and `'old.zip'`) leave the model with them, so no rows pass until a value is selected again.

#### Preserving Previous Values with Row Grouping

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GridApi,
  GridOptions,
  GridReadyEvent,
  KeyCreatorParams,
  ModuleRegistry,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  RowGroupingModule,
  SetFilterModule,
} from "ag-grid-enterprise";
import "./styles.css";

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

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ColumnMenuModule,
  SetFilterModule,
  RowGroupingModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="example-wrapper">
    <div style="margin-bottom: 5px">
      <button (click)="selectParisAndMadrid()">Select Paris and Madrid</button>
      <button (click)="removeRome()" style="margin-left: 5px">
        Remove Rome
      </button>
      <button (click)="removeFrance()">Remove France</button>
      <button (click)="restoreData()">Restore Data</button>
      <button (click)="clearPreservedValues()" style="margin-left: 5px">
        Clear Preserved Values
      </button>
      <button (click)="reset()" style="margin-left: 5px">Reset</button>
    </div>

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

  columnDefs: ColDef[] = [
    { field: "country", rowGroup: true, hide: true },
    { field: "city", hide: true },
    { field: "sales" },
  ];
  autoGroupColumnDef: AutoGroupColumnDef = {
    field: "city",
    minWidth: 250,
    filter: "agSetColumnFilter",
    floatingFilter: true,
    filterParams: {
      treeList: true,
      keyCreator: (params: KeyCreatorParams) =>
        params.value ? params.value.join("#") : null,
      preservePreviousValues: true,
    },
  };
  groupDefaultExpanded = -1;
  rowData: any[] | null = getRowData();

  selectParisAndMadrid() {
    // 'Spain#Madrid' is not in the data, so it is listed by its key.
    this.gridApi.setFilterModel({
      "ag-Grid-AutoColumn": {
        filterType: "set",
        values: ["France#Paris", "Spain#Madrid"],
      },
    });
  }

  removeRome() {
    this.gridApi.setGridOption(
      "rowData",
      this.gridApi
        .getGridOption("rowData")!
        .filter((row) => row.city !== "Rome"),
    );
  }

  removeFrance() {
    this.gridApi.setGridOption(
      "rowData",
      this.gridApi
        .getGridOption("rowData")!
        .filter((row) => row.country !== "France"),
    );
  }

  restoreData() {
    this.gridApi.setGridOption("rowData", getRowData());
  }

  clearPreservedValues() {
    this.gridApi.doFilterAction({
      colId: "ag-Grid-AutoColumn",
      action: "clearPreservedValues",
    });
  }

  reset() {
    this.gridApi.setFilterModel(null);
    this.gridApi.setGridOption("rowData", getRowData());
    this.gridApi.doFilterAction({
      colId: "ag-Grid-AutoColumn",
      action: "clearPreservedValues",
    });
  }

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

function getRowData() {
  return [
    { country: "France", city: "Paris", sales: 10 },
    { country: "France", city: "Lyon", sales: 20 },
    { country: "Italy", city: "Rome", sales: 30 },
    { country: "Italy", city: "Milan", sales: 40 },
  ];
}
```

[Live example: Preserving Previous Values with Row Grouping](https://www.ag-grid.com/archive/36.3.0/examples/filter-set-data-updates/preserve-previous-values-grouping/angular/)

#### Preserving Previous Values with Tree Data

```ts
import { Component } from "@angular/core";
import { AgGridAngular } from "ag-grid-angular";
import {
  AutoGroupColumnDef,
  ClientSideRowModelModule,
  ColDef,
  ColGroupDef,
  GetDataPath,
  GridApi,
  GridOptions,
  GridReadyEvent,
  KeyCreatorParams,
  ModuleRegistry,
  enableDevValidations,
} from "ag-grid-community";
import {
  ColumnMenuModule,
  SetFilterModule,
  TreeDataModule,
} from "ag-grid-enterprise";
import "./styles.css";

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

ModuleRegistry.registerModules([
  ClientSideRowModelModule,
  ColumnMenuModule,
  SetFilterModule,
  TreeDataModule,
]);

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="example-wrapper">
    <div style="margin-bottom: 5px">
      <button (click)="selectReportAndArchive()">
        Select report.pdf and old.zip
      </button>
      <button (click)="removeBeach()" style="margin-left: 5px">
        Remove beach.jpg
      </button>
      <button (click)="removeDocuments()">Remove Documents</button>
      <button (click)="restoreData()">Restore Data</button>
      <button (click)="clearPreservedValues()" style="margin-left: 5px">
        Clear Preserved Values
      </button>
      <button (click)="reset()" style="margin-left: 5px">Reset</button>
    </div>

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

  columnDefs: ColDef[] = [{ field: "size" }];
  autoGroupColumnDef: AutoGroupColumnDef = {
    headerName: "File",
    minWidth: 250,
    filter: "agSetColumnFilter",
    floatingFilter: true,
    filterParams: {
      treeList: true,
      keyCreator: (params: KeyCreatorParams) =>
        params.value ? params.value.join("/") : null,
      preservePreviousValues: true,
    },
  };
  getDataPath: GetDataPath = (data) => data.path;
  groupDefaultExpanded = -1;
  rowData: any[] | null = getRowData();

  selectReportAndArchive() {
    // 'Archive/old.zip' is not in the data, so it is listed by its key.
    this.gridApi.setFilterModel({
      "ag-Grid-AutoColumn": {
        filterType: "set",
        values: ["Documents/report.pdf", "Archive/old.zip"],
      },
    });
  }

  removeBeach() {
    this.gridApi.setGridOption(
      "rowData",
      this.gridApi
        .getGridOption("rowData")!
        .filter((row) => row.path[1] !== "beach.jpg"),
    );
  }

  removeDocuments() {
    this.gridApi.setGridOption(
      "rowData",
      this.gridApi
        .getGridOption("rowData")!
        .filter((row) => row.path[0] !== "Documents"),
    );
  }

  restoreData() {
    this.gridApi.setGridOption("rowData", getRowData());
  }

  clearPreservedValues() {
    this.gridApi.doFilterAction({
      colId: "ag-Grid-AutoColumn",
      action: "clearPreservedValues",
    });
  }

  reset() {
    this.gridApi.setFilterModel(null);
    this.gridApi.setGridOption("rowData", getRowData());
    this.gridApi.doFilterAction({
      colId: "ag-Grid-AutoColumn",
      action: "clearPreservedValues",
    });
  }

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

function getRowData() {
  return [
    { path: ["Documents"] },
    { path: ["Documents", "report.pdf"], size: 120 },
    { path: ["Documents", "notes.txt"], size: 4 },
    { path: ["Pictures"] },
    { path: ["Pictures", "beach.jpg"], size: 2400 },
    { path: ["Pictures", "city.jpg"], size: 1800 },
  ];
}
```

[Live example: Preserving Previous Values with Tree Data](https://www.ag-grid.com/archive/36.3.0/examples/filter-set-data-updates/preserve-previous-values-tree-data/angular/)

### Limiting Preserved Values

Selected values are always kept. Unselected values that are no longer in the data are kept up to `preservePreviousValuesLimit`:

| `preservePreviousValuesLimit` | Unselected values kept once they leave the data |
| --- | --- |
| not set | up to 1,000 |
| a number, for example `500` | up to that number |
| `0` | none: only selected values are kept, so the list shows the current values plus the selected ones |
| `-1` | all of them, with no limit, so memory use is unbounded |

When the limit is exceeded, the value that left the data first is discarded first. Discarding an unselected value does not change which rows pass the applied filter: it is not in the model, so if its rows return it is added back as a new, unselected value and its rows stay filtered out, exactly as if it had been kept. The limit only decides which of these values stay listed, and in memory, while their rows are away.

Values are discarded when the values next reload, for example when the data changes or `refreshFilterValues` is called, not when a value is deselected. Only the applied filter model, and an applied Advanced Filter expression, protect a value: with an **Apply** button, a value selected but not yet applied can be discarded by such a reload.

While the filter is inactive there is no model, so every retained value counts as unselected and the limit still applies. If a filter is applied later, a discarded value that returns is added back as a new, unselected value.

### Clearing Preserved Values

Call `api.doFilterAction()` with the `clearPreservedValues` action to discard the preserved values that are not in the current data. Set `colId` to a column ID, or an array of IDs, to clear only those; without it, every column is cleared. This works whether or not `enableFilterHandlers` is set:

```ts
this.gridApi.doFilterAction({ colId: 'col1', action: 'clearPreservedValues' });
this.gridApi.doFilterAction({ colId: ['col1', 'col2'], action: 'clearPreservedValues' });
this.gridApi.doFilterAction({ action: 'clearPreservedValues' });
```

The values are cleared wherever the column keeps them: its Set Filter, a Set Filter inside a [Multi Filter](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-multi/) or a selectable filter, and the [Advanced Filter](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-advanced/). Each filter model is then reconciled with the remaining values, as happens without `preservePreviousValues`: selected values that were discarded leave the model, and the filter becomes inactive if every remaining value is selected. The values an applied Advanced Filter expression names are kept, as the expression still uses them.

The `clearUnselectedPreservedValues` action discards only the values that are not in the applied filter model. Those in it stay listed, and the model is left as it is:

```ts
this.gridApi.doFilterAction({ colId: 'col1', action: 'clearUnselectedPreservedValues' });
```

### Lifetime and Memory

A filter only sees values while it exists, so a column with `preservePreviousValues` creates its filter up front rather than when it is first used:

- Its filter is created once the grid knows the column's data type. While the [Advanced Filter](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-advanced/) is enabled, the Advanced Filter creates its own instead. Pivot result columns, which copy their value column's params, do not create one.
- A `values` callback is therefore called when the grid starts.
- A Set Filter inside a [Multi Filter](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-multi/) is created up front with the whole Multi Filter, including its other filters. A selectable filter only creates the filter that is selected, so a Set Filter there keeps values only while it is selected.

Values are kept for the life of the filter. They are discarded if the filter is destroyed, for example with `api.destroyFilter`, after which the filter starts again from the current data. Turning the option off discards them too, as does turning the Advanced Filter on or off, which replaces the filters that kept them. They are also discarded when the rules that make their keys change:

- A column definition update that changes how values are identified, such as `caseSensitive`, as listed in [Updating Set Filter Parameters](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set/#updating-set-filter-parameters). With a filter applied, this resets the Set Filter. The values an applied Advanced Filter expression names stay listed, by their key.
- For a [Tree List](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/filter-set-tree-list/) on a group column that takes its values from the grid, the row group columns, `groupAllowUnbalanced` with Row Grouping, or Tree Data. These change the keys themselves, so model values that no longer match any value leave the model.

Preserved values are not saved with [Grid State](https://www.ag-grid.com/archive/36.3.0/angular-data-grid/grid-state/): a saved model restores the selected values, and unselected values stay filtered out because they are not in the model.

Keeping values also keeps them in memory. Columns whose values change constantly, such as timestamps, unique IDs or frequently edited cells, add a value for every change, and a value that is a complex object keeps that object alive. Lower `preservePreviousValuesLimit` for such columns, or set it to `0` to keep only what is selected.

Each filter that takes its values from the grid reloads them from every row when the data changes, and reads every row again when another column's filter changes. Creating filters up front means every opted-in column pays that from the start, whether or not its filter is ever opened. Enable the option on the columns that need it rather than through `defaultColDef` on a wide grid.
