8

Is there any way to have dynamic object properties in a TypeScript class, and add dynamic Typings in for TypeScript?

I have seen similar questions but none with a complete example like this -

interface IHasObjectName {
   objectName: string;
}

class example<A extends IHasObjectName, B  extends IHasObjectName> {

    constructor(a: A, b: B) {
        this[a.objectName] = function() { return a; };
        this[b.objectName] = function() { return b; }
    }
}

class Cat implements IHasObjectName {
    objectName: string = "";
}

class Dog implements IHasObjectName {
    objectName: string = "";
}

let cat = new Cat();
cat.objectName = "Cat";

let dog = new Dog();
dog.objectName = "Dog";

let test = new example<Cat,Dog>(cat, dog);

// ??? TYPESCRIPT DOESN'T KNOW ABOUT THESE DYNAMIC PROPERTIES
// HOW DO I MAKE THIS WORK?
let d = test.Dog();
let c = test.Cat();

// I know I could access like this 
// let d = test["Dog"](); 
// but I want to access like function and have it typed
2
  • I have a feeling that we are missing your real use-case here. Is this code really representative for what you want to do? Are you just using dynamic object properties in this example as a way to achieve what you really want, or is dynamic properties really your main interest here? Commented Dec 1, 2016 at 16:42
  • You could just use Javascript Commented Nov 28, 2023 at 17:09

2 Answers 2

11

You can use a factory function and intersection:

function factory<A extends IHasObjectName, B extends IHasObjectName, C>(a: A, b: B): example<A, B> & C {
    return new example<Cat, Dog>(a, b) as C;
}
var test = factory<Cat, Dog, { Dog(): Dog, Cat(): Cat }>(cat, dog);

var d = test.Dog(); // no error
var c = test.Cat(); // no error

(code in playground)


Edit

You can't "reflect" types because they don't exist in runtime, but you can use the constructor.name of the passed in instances, so you can simply do this:

class example<A, B> {
    constructor(a: A, b: B) {
        this[a.constructor.name] = function() { return a; };
        this[b.constructor.name] = function() { return b; }
    }
}

class Cat {}

class Dog {}

var cat = new Cat();
var dog = new Dog();


function factory<A, B, C>(a: A, b: B): example<A, B> & C {
    return new example<Cat, Dog>(a, b) as C;
}
var test = factory<Cat, Dog, { Dog(): Dog, Cat(): Cat }>(cat, dog);

var d = test.Dog();
var c = test.Cat();

(code in playground)

Sign up to request clarification or add additional context in comments.

14 Comments

In the factory, use the parameters: return new example<Cat, Dog>(a, b) as C; ;)
That is really cool, I learned something there. I just wish I could avoid the users of the factory having to basically type the methods when calling the factory ( interface type "C"). My goal is just to have test.Dog appear like magic. Might not be possible.
@user210757 it's not possible. You're changing the instance at runtime and the compiler has no way of knowing that at compile time.
i had to do this return new example<Cat, Dog>(a, b) as example<A, B> & C; in my specific code (different than example) to get past compiler errors, not sure why. But I guess that would make any other public methods in original "example" class available as well as "C" interface
@user210757 makes sense
|
-1

You need to cast it to any object type if you wan't "JavaScript behavior" in TypeScript.

There are two syntax types, which are equivalent:

const d = (<any>test).Dog();
const c = (<any>test).Cat();

and

const testAny = test as any;
const d = testAny.Dog();
const c = testAny.Cat();

the last one was created for support in tsx files, where wouldn't work and is now the recommended way.

There is hardly an other way to do that other than use the indexer, since the properties are dynamic and not typed.

BTW I encourage to use const and let instead of var.

1 Comment

Thanks, that essentially eliminates my typing benefits though. And intellisense.

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.