AG Grid: Typescript Generics
Tags: typescript, productivity, webdev
Published: 2022-07-14 10:31:53

In this article, we will show you how to make the most of Typescript Generics in AG Grid [v28](https://blog.ag-grid.com/whats-new-in-ag-grid-28/). We will demonstrate the great developer experience it unlocks with the help of code examples.

## AG Grid Generic Types

There are two generic types that you can pass to AG Grid interfaces. They are: `TData` and `TValue`.  

### TData - Row Data Type

`TData` is used to represent the shape of row data items. This generic parameter should match the interface used when defining your row data. If an AG Grid interface has a `TData` generic parameter this will always refer to the row data type.

```ts
interface GridOptions<TData = any>{
    rowData: TData[] : null;
}
```

To maintain backwards compatibility a default type of `any` is provided. This mimics the behaviour of previous versions of AG Grid that explicitly type the rowData as `any[]`.


### TValue - Cell Value Type

`TValue` is used to represent the type of a specific cell value. This generic type is more limited in scope and can be used within cell renderers/value getters when you want to give a type to the `value` property.

```ts
export interface GetQuickFilterTextParams<TData = any, TValue = any> {
    /** Value for the cell. */
    value: TValue;
}
```

As with `TData`, we also default `TValue` to `any` to maintain backwards compatibility.

## Using Generic Types

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

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

var rowData: ICar[] = [
  { make: "Toyota", model: "Celica", price: 35000 },
  { make: "Porsche", model: "Boxster", price: 72000 }
];
```

The best place to assign `ICar` to the `TData` generic parameter is via the `GridOptions` interface. This will cascade the generic type down the interface hierarchy. 

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

With the generic parameter set to `ICar`, the `rowData` property has the type `ICar[]` instead of the default `any[]`. This means that if you mistype one of the properties you will get a compile-time error.

![Row Data enforces ICar interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jkudnm386ilzudfmlr3d.png)


It is worth noting that it is not just `rowData` where the interface is used. For example, in the `getRowId` callback, the `params.data` property is typed as `ICar` instead of `any`. This is a result of AG Grid cascading the generic type down from `GridOptions`.

This also applies to all the grid events that contain a data property. i.e `onRowSelected`.

```ts
const gridOptions: GridOptions<ICar> = {
    // 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;
        }
    }
}
```

If you use the grid api via `gridOptions.api` or callback `params.api` properties, then the api will be aware of the row data type too. This means that the method `api.getSelectedRows()` will return rows as `ICar[]`.

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

### Configure via Individual Interfaces

While it is possible to configure everything inline within a `gridOptions` object, you may want to split things out for readability / re-usability. To benefit from Typescript generics we can provide our `ICar` interface to any AG Grid interface that accepts a `TData` generic parameter. 

For example, we could configure our event handler with 
 `RowSelectedEvent<ICar>`. This results in the `event.data` property being typed as `ICar | undefined`. (See *Generic Type or Undefined* below for more details on the additional `undefined` type).

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

## Advantages over any

There are two main reasons to provide generic types to AG Grid:

 - Compile-time errors
 - Auto-completion in your IDE

### Compile Time Errors

Say we mistyped `price` as `prive`. Without generics, your application code would compile but your grid would no longer show prices in the grid. You would then have to track down the source of the bug. It could even be possible that this type of bug escapes into production if it slips past your tests. 

However, if you supply generic types your application would not compile and would tell you the exact location of the bug. This enables you to instantly correct the code, instead of having to track down the bug. It will also be underlined with a red squiggle in your IDE! 

![Compile time error for incorrect property name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qfbit9jbag266me45s5s.png)

### Auto-Completion

As Typescript knows that our `data` object conforms to the `ICar` interface our IDE can suggest properties to us. This speeds up your development process as you do not have to spend time thinking about property names as they are suggested to you automatically. 

![Auto completion of data properties based on generic type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu459i2hbchhx81kush2.png)

## Framework Specific Demo

### React

If you are using AG Grid via the React component then you can pass your row data type to the `AgGridReact` component via this syntax: `<AgGridReact<ICar>`.

This will validate that all `props` conform to the row data type. 

```jsx
<AgGridReact<ICar>
   ref={gridRef}
   rowData={rowData}
   columnDefs={columnDefs}
   onGridReady={onGridReady}
></AgGridReact>
```

If you provide conflicting types you will get a compile-time error. For example, here we have provided a different generic type, `INotCar` to the `onGridReady` event which does not match our `rowData` of `ICar`.

![React validating generic types are consistent](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rvg9rmigrytqqqt9xagx.png)

### Angular

For Angular, providing a type to your `rowData` property is enough to enable generics for the entire component. 

```ts
 public rowData: ICar[];
```
```html
 <ag-grid-angular
      [columnDefs]="columnDefs"
      [rowData]="rowData"
      (gridReady)="onGridReady($event)"
    ></ag-grid-angular>
```

If you are not using the `rowData` property, then type any other input/output that takes the generic parameter. For example, setting the following type on the `onGridReady` event would set the generic type for the entire component.

```ts
onGridReady(event: GridReadyEvent<ICar>) { }
``` 

If you provide conflicting types you will get a compile-time error. For example, here we have provided a different generic type, `INotCar` to the `onGridReady` event which does not match our `rowData` type of `ICar`.

![Angular validating generic types are consistent](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylxp83ir65wbvjh9z0lk.png)


## Generic Type or Undefined

One thing you may notice when adding generic support to an existing application is that you may get errors relating to the `data` property potentially being `undefined`. You will not have seen this previously as the `data` property was typed as `any` which silently includes `undefined`. By specifying the type explicitly, AG Grid has the opportunity to warn you, via typings, that the data property could be `undefined` under some circumstances.

To demonstrate this we will look at a [common pitfall](https://ag-grid.com/angular-data-grid/component-cell-renderer/#data-in-cell-renderers) that users have when adding custom cell renderers while supporting row grouping. 

### Common Pitfall with Cell Renderers

When writing a custom cell renderer you are likely to  access the `data` property from the `ICellRendererParams` interface. Your first attempt may have code that accesses data properties with no undefined checks.

```ts
init(params: ICellRendererParams) => {
   this.total = params.data.gold + params.data.silver + params.data.bronze;
};
```

This may work in your application but it is not technically correct or future-proof.

If you have row grouping enabled then as soon as the user groups by a column your cell renderer will fail. This is because when grouping the `data` property is `undefined`. 

However, if you provide a generic type to `ICellRendererParams`, such as `IOlympicData`, your code will warn that the `data` property can be `undefined`.

![params.data can be undefined](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5lmvk3ekt7nabfrfvalj.png)

With the generic type supplied you are more likely to correctly write the cell renderer to handle this case.

```ts
init(params: ICellRendererParams<IOlympicData>) => {
    if(params.data){
        this.total = params.data.gold + params.data.silver + params.data.bronze;
    }else{
        this.total = undefined;
    }
};
```

> While it is possible to silence the "Object is possibly 'undefined'" errors with a non-null operator `!.` such as `params.data!.gold`, we would recommend writing your code defensively in line with the AG Grid interfaces. 

## Conclusion

If you are using Typescript then we would encourage you to take advantage of our generic types to give you: 

 - Compile-time errors
 - Auto-completion in your IDE
 - Improved code typings 

It is a small change in your type definitions that results in a greatly improved developer experience!


For more details visit our [Typescript Generics](https://ag-grid.com/javascript-data-grid/typescript-generics/) documentation.