Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript dynamic class methods

Tags:

typescript

Problem

How can one add type checking to a dynamically created class method?

Example

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?

Notes

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.

like image 880
lucalanca Avatar asked Jul 08 '15 21:07

lucalanca


2 Answers

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();
like image 67
Vadim Macagon Avatar answered Sep 18 '22 17:09

Vadim Macagon


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
like image 31
Ryan Cavanaugh Avatar answered Sep 18 '22 17:09

Ryan Cavanaugh