Search code examples
angulartypescriptag-gridtypescript-genericsag-grid-angular

Create a dynamic type for row data in ag grid


I am trying to develop an independent component using ag-grid. I will pass the column definitions to this independent component from my application so I set it up as an input property with a type like this:

interface ColumnDef {
  field: string;
  headerName: string;
}

@Input() colDef: ColumnDef[];

Now as you can see I can pass any number of columns I want from my host(where i will use this common component) application with a different id and that is working fine.

Now the requirement is I want to define the type for row data for ag-grid inside the common component dynamically also.

Suppose i pass column defs as:

[{field: 'colA', headerName: 'Col A'},
{field: 'colB', headerName: 'Col B'}]

So when the component receive this column defs, it should create dynamically a row data type as

interface rowData {
 colA: string;
 colB: string;
}

Similarly if i pass column definitions as

[{field: 'colA', headerName: 'Col A'},
{field: 'colB', headerName: 'Col B'},
{field: 'colC', headerName: 'Col C'}]

the row data type should be crated as

interface rowData {
 colA: string;
 colB: string;
 colC: string;
}

so that I can use this interface to type my row data. Is there any way to implement this?

Thanks in advance.


Solution

  • Given some array like this:

    const colDef = [{field: 'colA', headerName: 'Col A'},
    {field: 'colB', headerName: 'Col B'},
    {field: 'colC', headerName: 'Col C'}] as const;
    

    we can have these types:

    type FieldNames<T> = T extends ReadonlyArray<{ field: infer U }> ? U : never;
    
    type Rows<T> = FieldNames<T> extends string ? Record<FieldNames<T>, string> : never;
    

    so that both

    • FieldNames<typeof colDef> is 'colA' | 'colB' | 'colC'
    • Rows<typeof colDef> is { colA: string; colB: string; colC: string }.

    However, with your current ColumnDef type, typeof colDef is ColumnDef, and so FieldNames<typeof colDef> will be Record<string, string>, which is not particularly helpful. To fix this, we will need to add a type parameter to the component class like so:

    class CustomGridComponent<T extends ColumnDef> implements OnInit /* or what have you */ { 
        @Input() colDef: ReadonlyArray<T> = [] as const;
        ... 
    }
    

    (by the way, using ReadonlyArray lets you get the types of your column definitions 'for free' by adding as const after the end of your array. If you are fine with manually defining some interface ABCColDef { field: 'colA' | 'colB' | 'colC'; ... } then ReadonlyArray is not necessary.)