---
title: "TypeScript Generics"
framework: angular
version: "36.1.0"
---

# TypeScript Generics

AG Grid supports TypeScript [Generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) for row data, cell values and grid context. This leads to greatly improved developer experience via code completion and compile time validation of row data and cell value properties.

## Row Data: <TData>

Provide a TypeScript interface for row data to the grid to enable auto-completion and type-checking whenever properties are accessed from a row `data` variable. There are multiple ways to configure the generic interface: via the `GridOptions<TData>` interface, via other individual interfaces and finally via framework components.

In the examples below we will use the `ICar` interface to represent row data.

```js
// Row Data interface
interface ICar {
    make: string;
    model: string;
    price: number;
}
```

### Configure via GridOptions

Set the row data type on the grid options interface via `GridOptions<ICar>`. The `ICar` interface will then be used throughout the grid options whenever row data is present. This is true for: properties, callbacks, events and the gridApi.

```js
// Pass ICar to GridOptions as a generic
const gridOptions: GridOptions<ICar> = {
    // rowData is typed as ICar[]
    rowData: [ { make: 'Ford', model: 'Galaxy', price: 20000 } ],

    // Callback with params type: GetRowIdParams<ICar>
    getRowId: (params) => {
        // params.data : ICar
        return params.data.make + params.data.model;
    },

    // Event with type: RowSelectedEvent<ICar>
    onRowSelected: (event) => {
        // event.data: ICar | undefined
        if (event.data) {
            const price = event.data.price;
        }
    }
}

// Grid Api methods use ICar interface
function onSelection() {
    // api.getSelectedRows() : ICar[]
    const cars: ICar[] = api!.getSelectedRows();
}
```

> **Note**
>
> You do not need to explicitly type callbacks and events that are defined as part of `GridOptions`. TypeScript will correctly pass the generic type down the interface hierarchy.

### Configure via Interfaces

Each interface that accepts a generic type of `TData` can also be configured individually. For example, an event handler function can accept the generic parameter on the event `RowSelectedEvent`.

```ts
function onRowSelected(event: RowSelectedEvent<ICar>) {
    if (event.data) {
        // event.data: ICar | undefined
        const price = event.data.price;
    }
}
```

### Configure via Component

The `<ag-grid-angular>` component is generic with respect to `TData`. As there is no way to explicitly set generic parameters on Angular components, Angular infers the `TData` generic type from Inputs / Outputs. However, it will not always be successful, leaving `TData` inferred as `any`.

We recommend providing the generic `TData` type to your `columnDefs` property as in practice we see this leading to the most accurate inference.

To provide the generic type of `ICar` to the `columnDefs` property you use the type `ColDef<ICar>` as follows:

```ts
const columnDefs: ColDef<ICar>[] = [...];
```

Then set this on your component template as `[columnDefs]="columnDefs"`. This provides Angular with the information it needs to assigns `ICar` to `TData` for the component.

```html
<ag-grid-angular
    [columnDefs]="columnDefs"
    (rowSelected)="onRowSelected($event)"
    />
```

This generic parameter is used for all other Inputs and Outputs ensuring consistency across the component. If `(rowSelected)` is defined with a different interface the application code will fail to compile.

```ts
// ERROR: INotACar is not assignable to ICar
onRowSelected(event: RowSelectedEvent<INotACar>) {}

// SUCCESS: ICar is correct interface
onRowSelected(event: RowSelectedEvent<ICar>) {}
```

### Type: TData | undefined

For a number of events and callbacks, when a generic interface is provided, the `data` property is typed as `TData | undefined` instead of `any`. The undefined is required because it is possible for the `data` property to be undefined under certain grid configurations.

A good example of this is [Row Grouping](https://www.ag-grid.com/angular-data-grid/grouping/). The `onRowSelected` event is fired for both leaf and group rows. Data is only present on leaf nodes and so the event should be written to handle cases when `data` is undefined for groups.

```js
function onRowSelected(event: RowSelectedEvent<ICar>) {
    // event.data is typed as ICar | undefined
    if (event.data) {
        // Leaf row with data
        const price = event.data.price;
    } else {
        // This is a group row
    }
}
```

## Cell Value: <TValue>

When working with cell values it is possible to provide a generic interface for the `value` property. While this will often be a primitive type, such as `string` or `number`, it can also be a complex type. Using a generic for the cell value will enable auto-completion and type-checking.

### Configure via ColDef

Set the cell value type directly on the column definition interface via `ColDef<TData, TValue>` (e.g. `ColDef<ICar, number>`). This will be passed through to all properties in the column definition that use the cell value type.

### Configure via Interfaces

Each interface that accepts a generic type of `TValue` can also be configured individually. Here is an example of a `valueFormatter` for the price column. The `params.value` property is correctly typed as a `number` due to typing the params argument as `ValueFormatterParams<ICar, number>`.

```js
const colDefs: ColDef<ICar>[] = [
    {
        field: 'price',
        valueFormatter: (params: ValueFormatterParams<ICar, number>) => {
            // params.value : number
            return "£" + params.value;
        }
    }
];
```

The `TValue` generic type is also supported for cell renderers / editors by `ICellRendererParams<TData, TValue>` and `ICellEditorParams<TData, TValue>` respectively.

### Typed: TValue | null | undefined

For a number of events and callbacks when a generic interface is provided, the `value` property is typed as `TValue | null | undefined` instead of `any`. This is because it is possible for the `value` property to be `undefined` under certain grid configurations, and it can be `null` when cell editing is enabled and the value has been deleted.

## Context: <TContext>

The grid options property `context` can be used to provided additional information to grid callbacks and event handlers implemented by your application. See [Context](https://www.ag-grid.com/angular-data-grid/context/) for more details. The `params.context` property can be typed via the `TContext` generic parameter.

### Configure via Interfaces

The generic parameter `TContext` needs to be explicitly provided to each interface where it is used. For example, an event handler function can accept the generic parameter on the event `RowSelectedEvent<TData, TContext>`.

```js
// Define the interface for your context
interface IDiscountRate {
    discount: number;
}

// Set the context property on gridOptions using `as` to apply the type
const gridOptions: GridOptions<ICar> {
    context: {
        discount: 0.9
    } as IDiscountRate;
}

// Provide to the interface to the TContext generic parameter to type the params.context property
function onRowSelected(event: RowSelectedEvent<ICar, IDiscountRate>) {
    if (event.data) {
        // event.context.discount is typed as number
        const price = event.data.price * event.context.discount;
    }
}
```

## Generic Type Example

Inspect the code in the following example or open in Plunker to experiment with generic typing yourself.

- `rowData` is typed using the `ICar` interface via `TData`.
- `valueFormatter` types the `value` property as `number` via `TValue`.
- `onRowSelected` event handler uses the `IDiscountRate` interface via `TContext`.

Also note that the `Log Selected Cars` button will log the selected cars to the developer console.

#### Generic Types

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

ModuleRegistry.registerModules([RowSelectionModule, ClientSideRowModelModule]);

interface ICar {
  make: string;
  model: string;
  price: number;
}
interface IDiscountRate {
  discount: number;
}

@Component({
  selector: "my-app",
  standalone: true,
  imports: [AgGridAngular],
  template: `<div class="test-container">
    <div class="test-header">
      <button (click)="onShowSelection()">Log Selected Cars</button>
    </div>
    <ag-grid-angular
      style="width: 100%; height: 100%;"
      class="test-grid"
      [columnDefs]="columnDefs"
      [rowData]="rowData"
      [rowSelection]="rowSelection"
      [context]="context"
      [getRowId]="getRowId"
      (rowSelected)="onRowSelected($event)"
      (gridReady)="onGridReady($event)"
    />
  </div> `,
})
export class AppComponent {
  private gridApi!: GridApi<ICar>;

  columnDefs: ColDef[] = [
    { headerName: "Make", field: "make" },
    { headerName: "Model", field: "model" },
    {
      headerName: "Price",
      field: "price",
      valueFormatter: (params: ValueFormatterParams<ICar, number>) => {
        // params.value: number
        return "£" + params.value;
      },
    },
  ];
  rowData: ICar[] | null = [
    { make: "Toyota", model: "Celica", price: 35000 },
    { make: "Ford", model: "Mondeo", price: 32000 },
    { make: "Porsche", model: "Boxster", price: 72000 },
  ];
  rowSelection: RowSelectionOptions | "single" | "multiple" = {
    mode: "multiRow",
  };
  context: any = {
    discount: 0.9,
  } as IDiscountRate;
  getRowId: GetRowIdFunc = (params: GetRowIdParams<ICar>) => {
    // params.data : ICar
    return params.data.make + params.data.model;
  };

  onRowSelected(event: RowSelectedEvent<ICar, IDiscountRate>) {
    // event.data: ICar | undefined
    if (event.data && event.node.isSelected()) {
      const price = event.data.price;
      // event.context: IContext
      const discountRate = event.context.discount;
      console.log("Price with 10% discount:", price * discountRate);
    }
  }

  onShowSelection() {
    // api.getSelectedRows() : ICar[]
    const cars: ICar[] = this.gridApi.getSelectedRows();
    console.log(
      "Selected cars are",
      cars.map((c) => `${c.make} ${c.model}`),
    );
  }

  onGridReady(params: GridReadyEvent<ICar>) {
    this.gridApi = params.api;
  }
}
```

[Live example: Generic Types](https://www.ag-grid.com/examples/typescript-generics/generic/angular)

## Fallback Default

If generic interfaces are not provided then the grid will use the default type of `any`. This means that generics in AG Grid are completely optional. GridOptions is defined as `GridOptions<TData = any>`, so if a generic parameter is not provided then `any` is used in its place for row data properties.

Likewise for cell values, if a generic parameter is not provided, `any` is used for the value property. For example, cell renderer params are defined as `ICellRendererParams<TData = any, TValue = any>`.
