Typescript dynamic class methods
Problem
How can you add type checking to a dynamically generated class method?
Example
Given a very simple class Property
.
class Property {
value: any;
name: string;
constructor(name: string, value: any) {
this.name = name;
this.value = value
}
}
and Entity
class
class Entity {
name: string;
properties: Property[];
constructor(name: string, properties: Property[]) {
this.name = name;
this.properties = properties;
this.properties.forEach((p: Property, index: number) => {
this[p.name] = (value: string): any => {
if (value) {
this.properties[index].value = value;
}
return this.properties[index].value;
}
}, this);
}
}
The important part: this[p.name] = function ...
(we don't know the name of the method at the time of "forwarding").
When uploading to javascript, we get the following error:
var car = new domain.Entity(
'car',
[
new domain.Property('manufacturer', 'Ford'),
new domain.Property('model', 'Focus')
]
);
car.model() // error TS2339: Property 'model' does not exist on type 'Entity'.
I know this is an unusual use of classes as different instances Entity
will have different methods. Is there a way to get rid of the error i.e. typescript is able to identify the correct interface for each instance, or at least disable the error ?
Notes
This is valid javascript and can be used like this:
var car = new domain.Entity(
'car',
[
new domain.Property('manufacturer', 'Ford'),
new domain.Property('model', 'Focus')
]
);
car.model() // 'Focus'
car.model('Transit') // 'Transit'
I know this was asked for a similar question , but this case is slightly different as the method name is also determined at runtime.
source to share
If you have dynamic properties that you want to access, use the any type to bypass the type checks on the variable. You can declare a variable of type any from get go, or use the type assertion operator ( like ) at some later point. Here are some possible options:
var car: any = new domain.Entity(...);
car.model();
var car = new domain.Entity(...) as any;
car.model();
var car = new domain.Entity(...);
(car as any).model();
source to share
Add this type (just one time):
interface Prop<T> {
(): T;
(value: T): T;
}
then you can write this for every shape you create:
interface Car extends Entity {
model: Prop<string>;
manufacturer: Prop<string>;
}
let car = <Car>new Entity('car', [/*...*/]);
car.model(32); // Error
let x = car.model(); // x: string
source to share