In my unique class implementation, I store a list of items in an object map rather than a flat array. Each property in the object map represents a specific group of items, similar to grouping cars by manufacturer.
// Definition of object map interface
interface IObjectMap<TValue> {
[key: string]: TValue;
}
// Definition of item map
type ItemMap<TMapKeys extends keyof IObjectMap<unknown>, TValue> = Record<TMapKeys, TValue[]>;
// Definition of getter function type
type GetterFunc<TInput, TResult> = (item: TInput) => TResult;
// Definition of getter map for object properties
type GetterMap<TMapKeys extends keyof IObjectMap<unknown>, TInput> = Record<TMapKeys, GetterFunc<TInput, string>>;
class GroupedItems<
TItem, // Type of items
TGroupKeys extends keyof IObjectMap<unknown> // Object map keys
> {
public groups: ItemMap<TGroupKeys, TItem> = {} as ItemMap<TGroupKeys, TItem>;
public countryGetters: GetterMap<TGroupKeys, TItem> = {} as GetterMap<TGroupKeys, TItem>;
public addItems(items: TItem[], getGroupKey: GetterFunc<TItem, TGroupKeys>): void {
this.items.concat(items);
this.items
.forEach(item => {
let name = getGroupKey(item);
if (this.groups[name] === undefined) {
// Create placeholder for items
this.groups[name] = [];
}
// Assign the item to the group
this.groups[name].push(item);
});
}
public assignGetters(getters: GetterMap<TGroupKeys, TItem>) {
this.countryGetters= getters;
}
}
The two generic type parameters for the class are:
- The type of items being grouped (e.g.,
Car
) - The group keys (e.g.,
'renault' | 'peugeot' | ...
)
Both class members are defined as object maps:
groups
stores items in group arrayscountryGetters
is also an object map with properties matching item groups, but with functions that return the manufacturer's country of the car
Example of Usage
Although the code appears free of errors, TypeScript doesn't resolve types correctly during usage. It should raise warnings when attempting to use a group name not defined in the map or union type of group keys...
interface Car {
model: string;
year: number;
}
interface CarMakers<TValue> extends IObjectMap<TValue> {
renault: TValue;
peugeot: TValue;
}
let select = new GroupedItems<
Car,
keyof CarMakers<unknown>
>();
select.addItems([
{ model: 'R5', year: 1980, dummy: false }, // error; correct
{ model: '206', year: 2004 },
{ model: '3008', year: 2010 }
],
car =>
car.year < 2000
? 'audi' // Should be an error; "audi" not in "keyof MakerGroups<>"
: 'peugeot'
);
select.assignGetters({
renault: () => 'France',
audi: () => 'Germany' // Should trigger an error; "audi" not in "keyof MakerGroups<>"
});
As depicted, the group names aren't resolved correctly by TypeScript, resulting in invalid manipulations on non-existent groups. The provided code looks fine from a compilation standpoint, but it needs additional checks and better intellisense support for filling up group names.