I am to use the following method, it works by passing a type to it such as obj.addComponent(MyClass)
. This works just fine.
I tried to modify the typ
There's no way to get the class using a name in javascript, it doesn't have something similar to the java ClassLoader
.
You can get around that by creating your own mechanism, and there are probably many ways to do so, but here are 3 options.
(1) Maintain a registry for your component classes:
const REGISTRY: { [name: string]: ComponentType<Component> } = {};
class Component {}
class MyComponent1 extends Component {}
REGISTRY["MyComponent1"] = MyComponent1;
class MyComponent2 extends Component {}
REGISTRY["MyComponent2"] = MyComponent2;
type ComponentType<T extends Component> = {
new(): T;
}
function factory<T extends Component>(type: ComponentType<T> | string): T {
return typeof type === "string" ?
new REGISTRY[type]() as T:
new type();
}
(code in playground)
If you go with this approach then I suggest to make the REGISTRY
an object that holds the collection, that way you can add the ctor only and get the name from that.
There's a variant for this and that's to use a decorator:
function register(constructor: typeof Component) {
REGISTRY[(constructor as any).name] = constructor;
}
@register
class MyComponent1 extends Component {}
@register
class MyComponent2 extends Component {}
(code in playground)
(2) Wrap the components in a namespace (As @Shilly suggested in a comment):
namespace components {
export class Component {}
export class MyComponent1 extends Component {}
export class MyComponent2 extends Component {}
export type ComponentType<T extends Component> = {
new(): T;
}
export function forName(name: string): ComponentType<Component> {
if (this[name] && this[name].prototype instanceof Component) {
return this[name];
}
}
}
function factory<T extends components.Component>(type: components.ComponentType<T> | string): T {
return typeof type === "string" ?
new (components.forName(type))() as T:
new type();
}
(code in playground)
If you're going with this approach then you need to make sure that all the component classes are exported.
(3) Use eval
class Component {}
class MyComponent1 extends Component {}
class MyComponent2 extends Component {}
type ComponentType<T extends Component> = {
new(): T;
}
function factory<T extends Component>(type: ComponentType<T> | string): T {
return typeof type === "string" ?
new (eval(type))() as T:
new type();
}
(code in playground)
This isn't a recommended approach, and you can read all about the cons in using eval
in a lot of places.
But it's still an option so I'm listing it.
There is a way to instantiate classes by their name as String if they are in a namespace :
var variableName: any = new YourNamespace[YourClassNameString](ClassParameters);
For exmaple, this should work :
namespace myNamespace {
export class myClass {
example() {
return true;
}
}
}
var newClass: any = new myNamespace["myClass"](); // <- This loads the class A.
newClass.example();
This will instantiate the class myClass
using the string "myClass"
.
Thus, to come back to your situation, I think this will work :
namespace myNamespace {
// The dependencies you defined
export class Component {
}
export interface ComponentType<T extends Component> {
new(): T;
}
// Just a class to contain the method's code
export class Example {
public addComponent<T extends Component>(type: ComponentType<T> | string): T {
let result: T;
if (typeof type === "string") {
result = new myNamespace[type]();
} else {
result = new type();
}
return result;
}
}
}
Then, you'll be able to do this :
let stringToLoad = "Component";
let classToLoad = Component;
let example = new Example();
let result1: Component = example.addComponent(stringToLoad);
let result2: Component = example.addComponent(classToLoad);
Playground version with code + test : here