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
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.
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.
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;
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