How to type an array with classes in TypeScript?
Solution 1
There is a working typescript playground (run it to get alert with result)
what we need is to create a custom type InterfaceComponent
. That will be expected as an array of the init()
method
interface IComponent { }
class TopBar implements IComponent { }
class StatusBar implements IComponent { }
class MainArea implements IComponent { }
// this is a type we want to be passed into INIT as an array
type InterfaceComponent = (typeof TopBar | typeof StatusBar | typeof MainArea);
class MyClass {
components: {[key:string] : IComponent } = {};
init(params: (InterfaceComponent)[]): void {
params.map((component) => {
let comp = new component();
this.components[comp.constructor["name"]] = comp;
}, this);
}
}
let x = new MyClass();
x.init([TopBar, StatusBar, MainArea])
alert(JSON.stringify(x.components))
Check it here
Solution 2
Typescript supports Class Type Generics (TypeScript Docs). Their example is:
function create<T>(c: {new(): T; }): T {
return new c();
}
Which says "Pass into my create
method a class that when constructed will return the type T that I want". This signature will prevent you from trying to pass in any class type that isn't of type T.
This is close to what we want, we just need to adjust for it being an array of items and items of your IComponent
.
public init(components: {new(): IComponent;}[]): void {
// at this point our `components` variable is a collection of
// classes that implement IComponent
// for example, we can just new up the first one;
var firstComponent = new components[0]();
}, this);
With the method signature, we can now use it like
app.init([TopBar, StatusBar, MainArea]);
Where we pass in the array of types that implement IComponent
Solution 3
Even though this is an old question: this is how you do it:
interface IComponent { something(): void; }
class TopBar implements IComponent { something() { console.log('in TopBar'); }}
class StatusBar implements IComponent { something() { console.log('in StatusBar'); }}
class MainArea implements IComponent { something() { console.log('in MainArea'); }}
interface ComponentClass {
new(): IComponent;
}
const components: { [name: string]: IComponent } = {};
function init(params: ComponentClass[]) {
params.map((component) => {
let comp = new component();
components[component.name] = comp;
});
}
init([TopBar, StatusBar, MainArea]);
for (const c in components) {
console.log('Component: ' + c);
components[c].something();
}
Related videos on Youtube
Sergei Basharov
Updated on July 09, 2022Comments
-
Sergei Basharov over 1 year
I have an app that initializes by running its method
.init(params)
like this:app.init([TopBar, StatusBar, MainArea]);
Where
TopBar
,StatusBar
andMainArea
are classes, not instances of classes. Each of these classes implements the same interfaceIComponent
.I want to instantiate objects from the passed classes in the
.init(params)
method, like this:init(params: IComponent[]): void { params.map(function (component) { let comp = new component(); this.components[comp.constructor.name] = comp; }, this);
The issue is that as these are not instance, TypeScript doesn't know their types and throws an error:
error TS2345: Argument of type '(typeof TopBar | typeof StatusBar | typeof MainArea)[]' is not assignable to parameter of type 'IComponent[]'.
How do I fix the code so that I could pass an array of classes that implement some interface to a method?
-
Sergei Basharov over 7 yearsThe last thing I want to do with TypeScript is assign everything's type to any.
-
fireydude over 7 yearsHow about assigning it InterfaceComponent
-
Sergei Basharov over 7 yearsThis is the most elegant solution, thanks! Though, it means, that, if I have a lot of components, like 100+, will I have to add them all to the type
InterfaceComponent
? -
Radim Köhler over 7 yearsWell, hard to say without deep dive... to follow the above approach.. we would need to extend it ... and that is not ok for 100+. Some Factory method, working with passed unknown type (not so type safe) would be more proper way to go, I'd say...
var instance = Object.create(objectType.prototype); instance.constructor.apply(instance, constructorParameters);
-
davestevens over 7 yearsThe inheritance is the wrong way round - InterfaceComponent needs to know all the classes that implement it, surely that's not a good thing?
-
jave.web over 1 yearoh! the
typeof
is the main magic here! I didn't even need the interface -: Set<typeof MyBaseModel>
was enough! :-)