I am trying to define a type based on ColDef<TData>
which infers the type of valueFormatter
from the given value of field
. For example, consider the following Order
type:
type Order = {
id: string,
product: string,
quantity: number,
timestamp: Date,
}
When defining a table of Order
rows in an Angular project, the column definitions will look something like:
const gridOptions: GridOptions<Order> = {
// Define 3 columns
columnDefs: [
{ field: 'id' },
{ field: 'product' },
{
field: 'quantity',
// params has inferred type ValueFormatterParams<Order, any>
// a stricter type would be ValueFormatterParams<Order, number>
valueFormatter: (params) => params.value.toLocaleString(),
},
],
// Other grid options...
};
My issue is that nothing stops me from adding the following column definition:
{
field: 'timestamp',
// Wrong type for params! The runtime type of params.value will be Date, not number!
valueFormatter: (params: ValueFormatterParams<Order, number>) => `${params.value + 1}`,
}
I want the above code to raise a type error from the incorrect type parameters. Is there any way to tell TypeScript that the type of valueFormatter
should be determined by the value of the given field
property?
I want something to this effect:
type StrictColDef<TData> = ColDef<TData> & {
field: TProperty extends keyof TData, // This gets inferred somehow
valueFormatter?: (params: ValueFormatterParams<TData, TData[TProperty]>): string,
};
type StrictGridOptions<TData> = GridOptions<TData> & { columnDefs: StrictColDef<TData>[] };
const gridOptions: StrictGridOptions<Order> = {
// Define 3 columns
columnDefs: [
{ field: 'id' },
{ field: 'product' },
{
field: 'quantity',
// params now has inferred type ValueFormatterParams<Order, number>, yay!
valueFormatter: (params) => params.value.toLocaleString(),
},
{
field: 'timestamp',
// type error because the compiler has inferred type for params.value: Date
valueFormatter: (params) => `${params.value + 1}`,
},
],
// Other grid options...
};
I've tried doing exactly this, but it just isn't how types are constructed in TypeScript.
I got a solution using type mapping! I wish there was a way to do this kind of type inference natively, but this is a pretty minimal workaround.
type Order = {
id: string,
price: number,
};
type AllColDefsAsProperties<T> = {
[Property in keyof T]: {
field: Property,
valueFormatter?: (value: T[Property]) => string,
}
};
type Values<T> = T[keyof T];
type ColDef<T> = Values<AllColDefsAsProperties<T>>;
const columnDefs: ColDef<Order>[] = [
{
field: "id",
// value has inferred type string
valueFormatter: (value) => value,
},
{
field: "price",
valueFormatter: Intl.NumberFormat("en-US", { currency: "USD" }).format,
},
{
field: "price",
// value has inferred type number, invalid return type!
//@ts-expect-error
valueFormatter: (value) => value,
},
// Compiler expects an object whose formatter has number as argument
//@ts-expect-error
{
field: "price",
valueFormatter: (value: string) => value,
},
];