546
votes

If I wanted to programatically assign a property to an object in Javascript, I would do it like this:

var obj = {};
obj.prop = "value";

But in TypeScript, this generates an error:

The property 'prop' does not exist on value of type '{}'

How am I supposed to assign any new property to an object in TypeScript?

24

24 Answers

708
votes

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.

93
votes

Or all in one go:

  var obj:any = {}
  obj.prop = 5;
75
votes

I tend to put any on the other side i.e. var foo:IFoo = <any>{}; So something like this is still typesafe:

interface IFoo{
    bar:string;
    baz:string;
    boo:string;     
}

// How I tend to intialize 
var foo:IFoo = <any>{};

foo.bar = "asdf";
foo.baz = "boo";
foo.boo = "boo";

// the following is an error, 
// so you haven't lost type safety
foo.bar = 123; 

Alternatively you can mark these properties as optional:

interface IFoo{
    bar?:string;
    baz?:string;
    boo?:string;    
}

// Now your simple initialization works
var foo:IFoo = {};

Try it online

72
votes

This solution is useful when your object has Specific Type. Like when obtaining the object to other source.

let user: User = new User();
(user as any).otherProperty = 'hello';
//user did not lose its type here.
39
votes

Although the compiler complains it should still output it as you require. However, this will work.

var s = {};
s['prop'] = true;
28
votes

One more option do to that is to access the property as a collection:

var obj = {};
obj['prop'] = "value";
24
votes

I'm surprised that none of the answers reference Object.assign since that's the technique I use whenever I think about "composition" in JavaScript.

And it works as expected in TypeScript:

interface IExisting {
    userName: string
}

interface INewStuff {
    email: string
}

const existingObject: IExisting = {
    userName: "jsmith"
}

const objectWithAllProps: IExisting & INewStuff = Object.assign({}, existingObject, {
    email: "[email protected]"
})

console.log(objectWithAllProps.email); // [email protected]

Advantages

  • type safety throughout because you don't need to use the any type at all
  • uses TypeScript's aggregate type (as denoted by the & when declaring the type of objectWithAllProps), which clearly communicates that we're composing a new type on-the-fly (i.e. dynamically)

Things to be aware of

  1. Object.assign has it's own unique aspects (that are well known to most experienced JS devs) that should be considered when writing TypeScript.
    • It can be used in a mutable fashion, or an immutable manner (I demonstrate the immutable way above, which means that existingObject stays untouched and therefore doesn't have an email property. For most functional-style programmers, that's a good thing since the result is the only new change).
    • Object.assign works the best when you have flatter objects. If you are combining two nested objects that contain nullable properties, you can end up overwriting truthy values with undefined. If you watch out for the order of the Object.assign arguments, you should be fine.
15
votes

You can create new object based on the old object using the spread operator

interface MyObject {
    prop1: string;
}

const myObj: MyObject = {
    prop1: 'foo',
}

const newObj = {
    ...myObj,
    prop2: 'bar',
}

console.log(newObj.prop2); // 'bar'

TypeScript will infer all the fields of the original object and VSCode will do autocompletion, etc.

9
votes

Simplest will be following

const obj = <any>{};
obj.prop1 = "value";
obj.prop2 = "another value"
9
votes

Since you cannot do this:

obj.prop = 'value';

If your TS compiler and your linter does not strict you, you can write this:

obj['prop'] = 'value';

If your TS compiler or linter is strict, another answer would be to typecast:

var obj = {};
obj = obj as unknown as { prop: string };
obj.prop = "value";
7
votes

To guarantee that the type is an Object (i.e. key-value pairs), use:

const obj: {[x: string]: any} = {}
obj.prop = 'cool beans'
7
votes

you can use this :

this.model = Object.assign(this.model, { newProp: 0 });
7
votes

Here is a special version of Object.assign, that automatically adjusts the variable type with every property change. No need for additional variables, type assertions, explicit types or object copies:

function assign<T, U>(target: T, source: U): asserts target is T & U {
    Object.assign(target, source)
}

const obj = {};
assign(obj, { prop1: "foo" })
//  const obj now has type { prop1: string; }
obj.prop1 // string
assign(obj, { prop2: 42 })
//  const obj now has type { prop1: string; prop2: number; }
obj.prop2 // number

//  const obj: { prop1: "foo", prop2: 42 }

Note: The sample makes use of TS 3.7 assertion functions. The return type of assign is void, unlike Object.assign.

7
votes

Case 1:

var car = {type: "BMW", model: "i8", color: "white"};
car['owner'] = "ibrahim"; // You can add a property:

Case 2:

var car:any = {type: "BMW", model: "i8", color: "white"};
car.owner = "ibrahim"; // You can set a property: use any type
6
votes

It is possible to add a member to an existing object by

  1. widening the type (read: extend/specialize the interface)
  2. cast the original object to the extended type
  3. add the member to the object
interface IEnhancedPromise<T> extends Promise<T> {
    sayHello(): void;
}

const p = Promise.resolve("Peter");

const enhancedPromise = p as IEnhancedPromise<string>;

enhancedPromise.sayHello = () => enhancedPromise.then(value => console.info("Hello " + value));

// eventually prints "Hello Peter"
enhancedPromise.sayHello();
4
votes

Store any new property on any kind of object by typecasting it to 'any':

var extend = <any>myObject;
extend.NewProperty = anotherObject;

Later on you can retrieve it by casting your extended object back to 'any':

var extendedObject = <any>myObject;
var anotherObject = <AnotherObjectType>extendedObject.NewProperty;
3
votes

The best practice is use safe typing, I recommend you:

interface customObject extends MyObject {
   newProp: string;
   newProp2: number;
}
3
votes

To preserve your previous type, temporary cast your object to any

  var obj = {}
  (<any>obj).prop = 5;

The new dynamic property will only be available when you use the cast:

  var a = obj.prop; ==> Will generate a compiler error
  var b = (<any>obj).prop; ==> Will assign 5 to b with no error;
2
votes

You can add this declaration to silence the warnings.

declare var obj: any;
1
votes

If you are using Typescript, presumably you want to use the type safety; in which case naked Object and 'any' are counterindicated.

Better to not use Object or {}, but some named type; or you might be using an API with specific types, which you need extend with your own fields. I've found this to work:

class Given { ... }  // API specified fields; or maybe it's just Object {}

interface PropAble extends Given {
    props?: string;  // you can cast any Given to this and set .props
    // '?' indicates that the field is optional
}
let g:Given = getTheGivenObject();
(g as PropAble).props = "value for my new field";

// to avoid constantly casting: 
let k:PropAble = getTheGivenObject();
k.props = "value for props";
1
votes

dynamically assign properties to an object in TypeScript.

to do that You just need to use typescript interfaces like so:

interface IValue {
    prop1: string;
    prop2: string;
}

interface IType {
    [code: string]: IValue;
}

you can use it like that

var obj: IType = {};
obj['code1'] = { 
    prop1: 'prop 1 value', 
    prop2: 'prop 2 value' 
};
1
votes

Try this:

export interface QueryParams {
    page?: number,
    limit?: number,
    name?: string,
    sort?: string,
    direction?: string
}

Then use it

const query = {
    name: 'abc'
}
query.page = 1
1
votes

The only solution that is fully type-safe is this one, but is a little wordy and forces you to create multiple objects.

If you must create an empty object first, then pick one of these two solutions. Keep in mind that every time you use as, you're losing safety.

Safer solution

The type of object is safe inside getObject, which means object.a will be of type string | undefined

interface Example {
  a: string;
  b: number;
}

function getObject() {
  const object: Partial<Example> = {};
  object.a = 'one';
  object.b = 1;
  return object as Example;
}

Short solution

The type of object is not safe inside getObject, which means object.a will be of type string even before its assignment.

interface Example {
  a: string;
  b: number;
}

function getObject() {
  const object = {} as Example;
  object.a = 'one';
  object.b = 1;
  return object;
}
1
votes

Extending @jmvtrinidad solution for Angular,

When working with a already existing typed object, this is how to add new property.

let user: User = new User();
(user as any).otherProperty = 'hello';
//user did not lose its type here.

Now if you want to use otherProperty in html side, this is what you'd need:

<div *ngIf="$any(user).otherProperty">
   ...
   ...
</div>

Angular compiler treats $any() as a cast to the any type just like in TypeScript when a <any> or as any cast is used.