SwiftD
SwiftD

Reputation: 6069

How can I handle class instantiation dynamically

I dont have much experience with typescript and could do with some help dynamically handling object initialisation:

I have a series of classes which all have a parameters sub class in the following structure:

export class MainClass1 {
     getParameters(): MainClass1.Parameters | undefined;
     setParameters(value?: MainClass1.Parameters): void;
}

export namespace MainClass1 {
    export class Parameters{
    }
}

export class MainClass2 {
     getParameters(): MainClass2.Parameters | undefined;
     setParameters(value?: MainClass2.Parameters): void;
}

export namespace MainClass2 {
    export class Parameters{
    }
}

The above is code i'm consuming so I cannot edit the class constructor but what I need to do is go from a string. e.g 'MainClass1' to return an instantiated Mainclass1 with an instantiated MainClass1.Parameters object, so right now I have code for each class like the following:

case 'MainClass1':
    let mc1 : Lib.MainClass1 = new Lib.MainClass1();
    let mc1Params : Lib.MainClass1.Parameters = new Lib.MainClass1.Parameters();
    mc1.setParameters(mc1Params);
    return mc1;

the return type of the above is an interface like this:

export interface GenericMainClass {
    getParameters() : any;
    getId(): string;
    setId(value: string): void;
    getClassName(): string;
    getValue(): Lib.Value | undefined;
    setValue(value?: Lib.Value): void;
}

As I say what i would ideally like is to be able to handle this instantiation dynamically. Can anyone show me how to instantiate these classes as described from a string name, is this possible and then how I should present a generic wrapper around setParameters, i hoped something like setParameters<T>(value? : T) : void; on the interface might work, but typescript complains about incompatible types when I try that.

Any help would be appreciated

Upvotes: 0

Views: 86

Answers (1)

Jevgeni
Jevgeni

Reputation: 2574

There is no 100% valid way to handle it in Typescript, but you can try semi-hacky way:

function newClass<TClass extends Lib.GenericMainClass>(name: "Class1" | "Class2"): TClass {
    const Class = Lib[name];
    const instance = new Lib[name]() as any as TClass;
    const parameters = new Lib[name].Parameters();
    instance.setParameters(parameters);
    return instance;
}

const newInstanceOfClass1 = newClass("Class1");
const newInstanceOfClass2 = newClass("Class2");

const newInstanceOfClass1Typed = newClass<Lib.Class1>("Class1");
const newInstanceOfClass2Typed = newClass<Lib.Class2>("Class2");

Here is url to TypeScript Playground

Explanation:

  • Lib is a usual JS object here, and you can still refer to it's keys same as in usual JS.

  • as any as TClass is a hack, that forces TypeScript to cast unknown type to what you need to.

  • <TClass extends ...> is a Generic type, that used inside the newClass function and can be defined when you call the newClass function like this: newClass<MyType>

Without having it defined:

const newInstanceOfClass1 = newClass("Class1");

it will be typeof Lib.GenericMainClass

With having it defined:

const newInstanceOfClass1Typed = newClass<Lib.Class1>("Class1");

it will be typeof Lib.Class1

So you are free to choose, if you need GenericMainClass or particular class when using newClass

Notes:

If you want to have absolutely any name of the class, then define newClass function like this:

function newClass<TClass extends Lib.GenericMainClass>(name: string): TClass { ...

If you still want to have control all possible class names, then you can use enum:

enum ClassNames {
   Class1: "Class1",
   Class2: "Class2",
   ...
}
function newClass<TClass extends Lib.GenericMainClass>(name: ClassNames): TClass {
...

Upvotes: 1

Related Questions