Sophon Aniketos
Sophon Aniketos

Reputation: 737

Given an object of classes, how to type the object of initialized class instances in Typescript?

I have an object that looks like this:

const classMap = {service1: Service1, service2: Service2}

Where Service1 and Service2 are reference to classes, there can be a few hundreds classes.

Now I have an object that is dynamically created, which corresponds to this:

const instanceMap = {service1: new classMap.service1(), service2: new classMap.service2()}

And I want to type this instanceMap (type deduction failed here because the object is dynamically created).

The naive approach of "typeof classMap" doesn't work. When I use instanceMap.service1, I don't see the methods of Service1 classes in auto-complete. It seems the typeof operator is doing a typeof ServiceX, which erases all the methods of ServiceX class.

Upvotes: 1

Views: 36

Answers (1)

jcalz
jcalz

Reputation: 330366

You can make a mapped type over the properties of classMap, where you apply the InstanceType<T> utility type to each property value:

const classMap = { service1: Service1, service2: Service2 };
type ClassMap = typeof classMap;
type InstanceMap = { [K in keyof ClassMap]: InstanceType<ClassMap[K]> };

This is equivalent to

/* type InstanceMap = {
    service1: Service1;
    service2: Service2;
} */

And therefore you can annotate instanceMap with that type without error:

const instanceMap: InstanceMap = {
    service1: new classMap.service1(),
    service2: new classMap.service2()
}; // okay

Now, if you want to make instanceMap dynamically, then you will also need mapped types in your function, and make liberal use of type assertions inside the implementation, as the compiler isn't smart enough to follow the logic. For example:

function makeInstanceMap<T extends Record<keyof T, object>>(
    classMap: { [K in keyof T]: new () => T[K] }): T {
    return Object.fromEntries(
        Object.entries(classMap).map(([k, v]) => [k, new (v as any)()])
    ) as T;
}

const instanceMap = makeInstanceMap(classMap);
/* const instanceMap: {
    service1: Service1;
    service2: Service2;
} */

Playground link to code

Upvotes: 1

Related Questions