Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript to TypeScript: Intellisense and dynamic members

I have a JavaScript object which dynamically allows members to be bound as accessor properties to instances of the object:

Source

function DynamicObject(obj) {
    for (var prop in obj) {
        Object.defineProperty(this, prop, {
            get: function () { return obj[prop]; },
            set: function (value) { obj[prop] = value; },
            enumerable: true,
            configurable: false
        });
    }
}

Usage

var obj = new DynamicObject({
    name: "John Smith",
    email: "[email protected]",
    id: 1
});

When obj is created, the members of the constructor parameter are bound to obj as accessor properties. These show up in intellisense

Intellisense for dynamic members

I would like to know if it is possible to model this sort of behavior (including having intellisense) in TypeScript?

Notes

When you run this code in TypeScript, there is no intellisense becuase everything is any, so TypeScript doesn't really know what's going on.

like image 615
Matthew Layton Avatar asked Oct 06 '16 15:10

Matthew Layton


2 Answers

You can't. These are completely dynamic properties, added at runtime, so you can't possibly know what they are at compile-time. I would also argue that you don't want to know what they are that early; if you have constraints to enforce, they should just be stated on their own (first example below).

If your code depends on a set of accessors, you should put those in the interface or contract directly, because you know ahead of time that you expect them and should advertise that. You can use optional properties (with the accessor defined lower) to make that simpler:

interface HasSomeProps {
  foo: string;
  bar?: string;
}

class DoesTheProps implements HasSomeProps {
  set foo(value) { 
    // ...
  }
}

If you have a bunch of consistent (or semi-consistent) accessors, you can define an indexer on your type like:

interface AccessStrings {
  [key: string]: string;
}

That does not allow you to restrict the keys. If you want that, you should explicitly list the properties.

like image 132
ssube Avatar answered Oct 22 '22 01:10

ssube


I would like to know if it is possible to model this sort of behavior (including having intellisense) in TypeScript?

Yes.

You can assign a generic call signature to DynamicObject. You'll need to declare it as a variable:

var DynamicObject: new <T>(obj: T) => T = function (obj)
{
    for (var prop in obj)
    {
        Object.defineProperty(this, prop, {
            get: function () { return obj[prop]; },
            set: function (value) { obj[prop] = value; },
            enumerable: true,
            configurable: false
        });
    }
} as any;

This way, IntelliSense will treat the value returned from new DynamicObject as having the same type as the value passed in. Same property names, same property types. You'll get full autocomplete and type-safety.

Live demo on TypeScript Playground


If you have trouble wrapping your head around that part in the first line, it's the same as writing the following:

// Declare type (only exists during compile-time)
var DynamicObject: new <T>(obj: T) => T;

// Assign value (during runtime)
DynamicObject = function (obj)
{
    ...
} as any;
like image 27
John Weisz Avatar answered Oct 22 '22 01:10

John Weisz