9

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.

2 Answers 2

2

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
Sign up to request clarification or add additional context in comments.

2 Comments

I should maybe add this to the question, but my goal is to be able to create new Entity instances (with different properties) without having to change any code (e.g. from a JSON file). For that reason, this won't work, unfortunately. This is called Adaptive Object-Model if anyone is interested adaptiveobjectmodel.com/WICSA3/ArchitectureOfAOMsWICSA3.pdf
I've never seen that syntax before. Documentation link?
1

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();

1 Comment

Thanks Vadim, this solves the transpilation error. I didn't know about the as operator, it's very handy.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.