How can one add type checking to a dynamically created class method?
Given a very simple Property
class.
class Property {
value: any;
name: string;
constructor(name: string, value: any) {
this.name = name;
this.value = value
}
}
and an 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);
}
}
Important part: this[p.name] = function ...
(we don't know the name of the method at "transpile" time).
We get the following error when transpiling to javascript:
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 that this is a uncommon use of classes as different instances of Entity
will have different methods defined. Is there a way to get rid of the error, i.e. typescript is able to identify the proper interface per instance, or at least silent the error?
This is valid javascript and one could use it in the following manner:
var car = new domain.Entity(
'car',
[
new domain.Property('manufacturer', 'Ford'),
new domain.Property('model', 'Focus')
]
);
car.model() // 'Focus'
car.model('Transit') // 'Transit'
I'm aware that this has been asked on a similar question, but this case is slightly different as the method name is also defined at runtime.
If you have dynamic properties you want to access then use the any type to bypass type checks for the variable. You can either declare the variable with the any type from the get go, or use the type assertion operator (as) at some later point. So here are some possible variants:
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();
Add this type (just needed once):
interface Prop<T> {
(): T;
(value: T): T;
}
then you can write this for each 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
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