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:
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):
The grid has six columns, so let's create Star.ts file with a type that models the rows:
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):
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:
Notice that the Star
type is imported too. It is needed in the line that provides static names of Star properties:
const fieldName = (name: keyof Star) => name;
This little arrow function is later used while field mapping is defined:
{
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:
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 string
s containing six property names that exist in Star
. In fact, you could create such type manually:
type TheNames = "rank" | "magnitude" | "name" | "designation" | "distance" | "spectral";
and it would be the same as a type created with:
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 string
s:
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!