Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Typescript

Safer ag-Grid (React) Column Definitions with TypeScript

3.00/5 (3 votes)
5 Jun 2020CPOL3 min read 10.4K  
Safer Binding due to TypeScript's keyof Operator
A typo or change in row model in JavaScript will cause an empty column in the grid because an ag-Grid column is bound to a row property by assigning a string to field config. In this post, you will see a much safer binding possible due to TypeScript's keyof operator.

TL;DR

In JavaScript, an ag-Grid column is bound to a row property by assigning a string to field config. This is quite fragile: a typo or change in row model will cause an empty column in the grid. Fortunately, a much safer binding is possible, thanks to TypeScript's keyof operator. Check grid component example and demo page.

Safer With Types

I'm continuing my little (lockdown-boredom driven) series on ag-Grid usage with React. Previous posts (hooks, resize, renderers) were done with JavaScript, now it's time to present benefits TypeScript!

ag-Grid docs show an easy way of defining the binding between column and a row object property:

JavaScript
columnDefs: [
    { headerName: 'Athlete', field: 'athlete' } 
]

With the above column definition, the grid will look for athlete property (in objects passed as row data) to populate the Athlete column. Such column biding is very easy to setup, but it's also easy to break. If you make a typo or change property name in row data, the grid will render empty cells! You might try to remedy this with constants but why not let the TypeScript do the work for you?

Assuming that your React project has TypeScript enabled (easy-peasy with CRA), making type-safe column bindings requires just two things:

  • declaring a type (or an interface) that models grid rows
  • using property name retrieved with keyof to set column's field

Example (The Stars)

Let's say we want to show a grid with a list of brightest starts (live version):

Brightest stars grid... Click to enlarge...

The grid has six columns, so let's create Star.ts file with a type that models the rows:

JavaScript
type Star = {
    rank: number;
    magnitude: number;
    name: string;
    designation: string;
    distance: number;
    spectral: string;
}

export default Star;

Once we have the type, we can use it to define grid columns. Below is the entire (tsx) code needed to define grid component (in functional flavor):

JavaScript
import React from 'react';
import { ColDef } from 'ag-grid-community';
import { AgGridReact } from 'ag-grid-react';

import Star from './Star';
import brightestStars from './brightestStars'

import 'ag-grid-community/dist/styles/ag-grid.css';
import 'ag-grid-community/dist/styles/ag-theme-balham.css';

const fieldName = (name: keyof Star) => name;

const columnDefs: ColDef[] = [
    {
        headerName: 'Rank',
        field: fieldName('rank'),
        width: 80
    },
    {
        headerName: 'Visual magnitude (mV)',
        field: fieldName('magnitude'),
        width: 170
    },
    {
        headerName: 'Proper name',
        field: fieldName('name'),
        width: 180
    },
    {
        headerName: 'Bayer designation',
        field: fieldName('designation'),
        width: 150
    },
    {
        headerName: 'Distance (ly)',
        field: fieldName('distance'),
        width: 120
    },
    {
        headerName: 'Spectral class',
        field: fieldName('spectral'),
        width: 130
    }
];

const StarsGrid: React.FC = () => {
    return (
        <div className='ag-theme-balham'>
            <AgGridReact
                defaultColDef={{
                    sortable: true
                }}
                columnDefs={columnDefs}
                rowData={brightestStars}
            />
        </div>
    );
}

export default StarsGrid;

Have you noticed that the file has an import of ColDef from ag-grid-community package? This is not required but ag-Grid comes with type definitions and these make development a lot easier. Because columnDefs is declared as an array of ColDef, the IDE (I'm using Visual Studio Code) is able to offer suggestions on column properties and will instantly highlight any mistake:

ColDef type sugestions...

Notice that the Star type is imported too. It is needed in the line that provides static names of Star properties:

JavaScript
const fieldName = (name: keyof Star) => name;

This little arrow function is later used while field mapping is defined:

JavaScript
{
    headerName: 'Visual magnitude (mV)',
    field: fieldName('magnitude'),
    width: 170,
},

At first glance, it doesn't look very useful... The 'magnitude' is still a string? Let's see what happens if 'dsignation' typo is made and Star model is modified by removing the spectral property:

Mapping errors detected... Click to enlarge...

IDE immediately highlights the errors and TypeScript won't compile. Static typing FTW!

Demo App

The app was built in React 16.13.1, ag-Grid Community 23.1.1, TypeScript 3.7.0 and tested in Chrome 83, Firefox 76 and Edge 44.

If you want to run the app on your machine: clone the repo, do npm install and npm start (just like with any other project initiated with create-react-app)...

How Does It Work?

TypeScript 2.1 introduced keyof operator that creates a type that lists property names. In our case, keyof Star yields a type that has a union of strings containing six property names that exist in Star. In fact, you could create such type manually:

JavaScript
type TheNames = "rank" | "magnitude" | "name" | "designation" | "distance" | "spectral";

and it would be the same as a type created with:

JavaScript
type TheNames = keyof Star;

The keyof option is clearly a better choice because the type will be amended automatically whenever Star type changes its properties.

The fieldName arrow function used in column mappings makes use of keyof to limit the acceptable strings:

JavaScript
const fieldName = (name: keyof Star) => name;

Thanks to keyof, TypeScript will reject any string passed to fieldName function which does not belong to a union of Star property names. Nice, no runtime supersizes!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)