Index types
It is possible to denote obj
as any
, but that defeats the whole purpose of using typescript. obj = {}
implies obj
is an Object
. Marking it as any
makes no sense. To accomplish the desired consistency an interface could be defined as follows.
interface LooseObject {
[key: string]: any
}
var obj: LooseObject = {};
OR to make it compact:
var obj: {[k: string]: any} = {};
LooseObject
can accept fields with any string as key and any
type as value.
obj.prop = "value";
obj.prop2 = 88;
The real elegance of this solution is that you can include typesafe fields in the interface.
interface MyType {
typesafeProp1?: number,
requiredProp1: string,
[key: string]: any
}
var obj: MyType ;
obj = { requiredProp1: "foo"}; // valid
obj = {} // error. 'requiredProp1' is missing
obj.typesafeProp1 = "bar" // error. typesafeProp1 should be a number
obj.prop = "value";
obj.prop2 = 88;
Record<Keys,Type>
utility type
Update (August 2020): @transang brought this up in comments
Record<Keys,Type>
is a Utility type in typescript. It is a much cleaner alternative for key-value pairs where property-names are not known.
It's worth noting that Record<Keys,Type>
is a named alias to {[k: Keys]: Type}
where Keys
and Type
are generics.
IMO, this makes it worth mentioning here
For comparison,
var obj: {[k: string]: any} = {};
becomes
var obj: Record<string,any> = {}
MyType
can now be defined by extending Record type
interface MyType extends Record<string,any> {
typesafeProp1?: number,
requiredProp1: string,
}
While this answers the Original question, the answer here by @GreeneCreations might give another perspective on how to approach the problem.
TypeScript uses getter/setter syntax that is like ECMAScript4/ActionScript3.
class foo {
private _bar: boolean = false;
get bar(): boolean {
return this._bar;
}
set bar(value: boolean) {
this._bar = value;
}
}
That will produce this JavaScript, using the ECMAScript 5 Object.defineProperty()
feature.
var foo = (function () {
function foo() {
this._bar = false;
}
Object.defineProperty(foo.prototype, "bar", {
get: function () {
return this._bar;
},
set: function (value) {
this._bar = value;
},
enumerable: true,
configurable: true
});
return foo;
})();
So to use it,
var myFoo = new foo();
if(myFoo.bar) { // calls the getter
myFoo.bar = false; // calls the setter and passes false
}
However, in order to use it at all, you must make sure the TypeScript compiler targets ECMAScript5. If you are running the command line compiler, use --target
flag like this;
tsc --target ES5
If you are using Visual Studio, you must edit your project file to add the flag to the configuration for the TypeScriptCompile build tool. You can see that here:
As @DanFromGermany suggests below, if your are simply reading and writing a local property like foo.bar = true
, then having a setter and getter pair is overkill. You can always add them later if you need to do something, like logging, whenever the property is read or written.
Getters can be used to implement readonly properties. Here is an example that also shows how getters interact with readonly and optional types.
//
// type with optional readonly property.
// baz?:string is the same as baz:string|undefined
//
type Foo = {
readonly bar: string;
readonly baz?: string;
}
const foo:Foo = {bar: "bar"}
console.log(foo.bar) // prints 'bar'
console.log(foo.baz) // prints undefined
//
// interface with optional readonly property
//
interface iFoo {
readonly bar: string;
readonly baz?: string;
}
const ifoo:iFoo = {bar: "bar"}
console.log(ifoo.bar) // prints 'bar'
console.log(ifoo.baz) // prints undefined
//
// class implements bar as a getter,
// but leaves off baz.
//
class iBarClass implements iFoo {
get bar() { return "bar" }
}
const iBarInstance = new iBarClass()
console.log(iBarInstance.bar) // prints 'bar'
// accessing bas gives warning that bas does not exist
// on iBarClass but returns undefined
console.log(iBarInstance.bas) // prints 'undefined'
// note that you could define baz as a getter
// and just return undefined to remove the warning.
//
// class implements optional readonly property as a getter
//
class iBazClass extends iBarClass {
private readonly _baz?: string
constructor(baz?:string) {
super()
this._baz = baz
}
get baz() { return this._baz; }
}
const iBazInstance = new iBazClass("baz")
console.log(iBazInstance.bar) // prints bar
console.log(iBazInstance.baz) // prints baz
Best Answer
I just found the answer to this in another Stack Overflow question's answer.
Basically, you need to extend the existing
window
interface to tell it about your new property.