I need to pass a class reference
as a function parameter
and invoke a static method
on this class. In the future, I might need to create an instance of the class and added a constructor as example.
I have it working without typings:
class Messages {
getMessage(classRef: any): any {
return classRef.getMessage()
}
}
class myClassA {
constructor(public id: number, public message: string) {}
static getMessage(): string {
return 'hello from class A';
}
}
class myClassB {
constructor(public id: number, public message: string) {}
static getMessage(): string {
return 'hello from class B';
}
}
var messages = new Messages();
var messageA = messages.getMessage(myClassA);
var messageB = messages.getMessage(myClassB);
console.log(messageA) // 'hello from class A'
console.log(messageB) // 'hello from class B'
I am trying to type the class reference, possibly with generics but confused about how to go about this.
I tried doing something along the lines of getMessage<C>(classRef: {new(): C;}): any {}...
but none of that is working.
Can someone please explain how I can pass a class reference properly?
Assigning Generic ParametersBy passing in the type with the <number> code, you are explicitly letting TypeScript know that you want the generic type parameter T of the identity function to be of type number . This will enforce the number type as the argument and the return value.
TypeScript generic type They can be used to identify that specific called function as a type. Making the function itself unaware of which type it's working with. To identify a generic type, you must prefix the function with <Type> where Type is the generic variable. Note: We often use T for generic types.
TypeScript supports generic classes. The generic type parameter is specified in angle brackets after the name of the class. A generic class can have generic fields (member variables) or methods. In the above example, we created a generic class named KeyValuePair with a type variable in the angle brackets <T, U> .
Advantages of using Generics in TypeScript: By using generics we may safely store a single type of object without storing the other types too. By using generics we need not implement the typecasting of any variable or function at the time of calling.
Generally speaking, you have to refer to classes in typescript using their constructor type. However, as of typescript 2.8, there is a new InstanceType<T>
type in the standard lib that can extract the instance type from the constructor type. You can use that here to get the type safety you want.
For your snippet, you might add types like so:
class Messages {
getMessage<T extends {getMessage: () => string, new (...args: any[]): InstanceType<T>}>(classRef: T): string {
// Here, classRef is properly inferred to have a `getMessage` method.
return classRef.getMessage()
}
}
class myClassA {
constructor(public id: number, public message: string) {}
static getMessage(): string {
return 'hello from class A';
}
}
class myClassB {
constructor(public id: number, public message: string) {}
static getMessage(): string {
return 'hello from class B';
}
}
var messages = new Messages();
// messageA and messageB inferred to have type: string
// You can change that back to any if you want.
// myClassA and myClassB both assignable as the argument to
// getMessage, so no problem there.
var messageA = messages.getMessage(myClassA);
var messageB = messages.getMessage(myClassB);
console.log(messageA) // 'hello from class A'
console.log(messageB) // 'hello from class B'
The line
getMessage<T extends {getMessage: () => string, new (...args: any[]): InstanceType<T>}>(classRef: T): string {
is where the type safety comes from. That syntax says that whatever T
is, it has to have a method getMessage
(so if T is a class constructor, getMessage
has to be a static method), and the new (...args: any[]): InstanceType<T>
means that T
has to be a class constructor.
I've set the arguments for the constructor to be anything here, but if you know that the constructor will always take specific arguments, you could further narrow that. For your example, new (id: number, message: string): InstanceType<T>
would work.
Without typescript 2.8, you can't use InstanceType
. But you can still get type safety here by letting T
represent the instance type, and then setting the type of the parameter to be a wrapped type that uses T
as it's parameter. So for your example:
interface Messageable<T> {
new(...args: any[]): T;
getMessage(): string;
}
class Messages {
getMessage<T>(classRef: Messageable<T>): string {
return classRef.getMessage()
}
}
should work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With