Reputation: 142987
If I wanted to programmatically 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 do I assign any new property to an object in TypeScript?
Upvotes: 885
Views: 1053219
Reputation: 318
It's worth noting that the accepted answer, utilizing index signatures, doesn't allow for declaring any old key:value pair in your object UNLESS they can match the index signature as well.
type BadTypeDeclaration = { cantUseThisKey: string, [k: string]: number }
doesn't work because EVERY OTHER VALUE in the object needs to match the index signature's value type, so you'll be limited.
I, for example, wanted an object like the following:
type Handlers = { wildCard?: WildCardHandler, [k: string]: NormalHandler }
Unless my WildCardHandler in this case can match NormalHandler, this won't be valid. https://www.typescriptlang.org/docs/handbook/2/objects.html
The solution?
type Handlers = Record<'wildCard', WildCardHandler> | Record<string, NormalHandler>
It works against all intuition I have for typescript, and I hate it.
Upvotes: 1
Reputation: 41
If you don't want to use any
, you can use do this:
type Obj = Record<string, unknown>
var obj: Obj = {};
obj.prop = "value";
obj.prop2 = 1
Upvotes: 2
Reputation: 14037
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, using an index signature
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 typeUpdate (August 2020): @transang brought up the Record<Keys,Type>
utility type 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 thatRecord<Keys,Type>
is a named alias to{[k: Keys]: Type}
whereKeys
andType
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.
Upvotes: 1163
Reputation: 4729
Simply do this, and you can add or use any property. (I am using typescript version as "typescript": "~4.5.5"
)
let contextItem = {} as any;
Now, you can add any property and use it any where. like
contextItem.studentName = "kushal";
later you can use it as:
console.log(contextItem.studentName);
Upvotes: 3
Reputation: 371
Late but, simple answer
let prop = 'name';
let value = 'sampath';
this.obj = {
...this.obj,
[prop]: value
};
Upvotes: 26
Reputation: 492
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 = getTheGivenObject() as PropAble;
k.props = "value for props";
Upvotes: 1
Reputation: 384074
Use ES6 Map
whenever a map can take truly arbitrary values of fixed type, and optional properties otherwise
I think this is the guideline I'll go for. ES6 map can be done in typescript as mentioned at: ES6 Map in Typescript
The main use case for optional properties are "options" parameters of functions: Using named parameters JavaScript (based on typescript) In that case, we do know in advance the exact list of allowed properties, so the sanest thing to do is to just define an explicit interface, and just make anything that is optional optional with ?
as mentioned at: https://stackoverflow.com/a/18444150/895245 to get as much type checking as possible:
const assert = require('assert')
interface myfuncOpts {
myInt: number,
myString?: string,
}
function myfunc({
myInt,
myString,
}: myfuncOpts) {
return `${myInt} ${myString}`
}
const opts: myfuncOpts = { myInt: 1 }
if (process.argv.length > 2) {
opts.myString = 'abc'
}
assert.strictEqual(
myfunc(opts),
'1 abc'
)
And then I'll use Map when it is something that is truly arbitrary (infinitely many possible keys) and of fixed type, e.g.:
const assert = require('assert')
const integerNames = new Map<number, string>([[1, 'one']])
integerNames.set(2, 'two')
assert.strictEqual(integerNames.get(1), 'one')
assert.strictEqual(integerNames.get(2), 'two')
Tested on:
"dependencies": {
"@types/node": "^16.11.13",
"typescript": "^4.5.4"
}
Upvotes: 0
Reputation: 397
I wrote an article tackling this very topic:
Typescript – Enhance an object and its type at runtime
https://tech.xriba.io/2022/03/24/typescript-enhance-an-object-and-its-type-at-runtime/
Maybe you could take inspiration from Typescript concepts such:
Upvotes: 1
Reputation: 1004
I ran into this problem when trying to do a partial update of an object that was acting as a storage for state.
type State = {
foo: string;
bar: string;
baz: string;
};
const newState = { foo: 'abc' };
if (someCondition) {
newState.bar = 'xyz'
}
setState(newState);
In this scenario, the best solution would be to use Partial<T>
. It makes all properties on the provided type optional using the ?
token. Read more about it in a more specific SO topic about making all properties on a type optional.
Here's how I solved it with Partial<T>
:
type State = {
foo: string;
bar: string;
baz: string;
};
const newState: Partial<State> = { foo: 'abc' };
if (someCondition) {
newState.bar = 'xyz';
}
setState(newState);
This is similar to what fregante described in their answer, but I wanted to paint a clearer picture for this specific use case (which is common in frontend applications).
Upvotes: 0
Reputation: 2341
Although the compiler complains it should still output it as you require. However, this will work.
const s = {};
s['prop'] = true;
Upvotes: 52
Reputation: 4885
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
Upvotes: 12
Reputation: 35267
You can add this declaration to silence the warnings.
declare var obj: any;
Upvotes: -3
Reputation: 4328
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.
Upvotes: 4
Reputation: 8836
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";
Upvotes: 9
Reputation: 31819
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.
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;
}
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;
}
Upvotes: 4
Reputation: 74710
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
.
Upvotes: 12
Reputation: 7
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
Upvotes: -1
Reputation: 1934
you can use this :
this.model = Object.assign(this.model, { newProp: 0 });
Upvotes: 13
Reputation: 1741
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.
Upvotes: 23
Reputation: 1825
Simplest will be following
const obj = <any>{};
obj.prop1 = "value";
obj.prop2 = "another value"
Upvotes: 10
Reputation: 1242
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
any
type at all&
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
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).Upvotes: 52
Reputation: 13996
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'
};
Upvotes: 3
Reputation: 2272
It is possible to add a member to an existing object by
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();
Upvotes: 6
Reputation: 19302
To guarantee that the type is an Object
(i.e. key-value pairs), use:
const obj: {[x: string]: any} = {}
obj.prop = 'cool beans'
Upvotes: 7
Reputation: 1095
One more option do to that is to access the property as a collection:
var obj = {};
obj['prop'] = "value";
Upvotes: 27
Reputation: 1742
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;
Upvotes: 3
Reputation: 856
The best practice is use safe typing, I recommend you:
interface customObject extends MyObject {
newProp: string;
newProp2: number;
}
Upvotes: 4
Reputation: 3658
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.
Upvotes: 116
Reputation: 57
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;
Upvotes: 3
Reputation: 276225
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 = {};
Upvotes: 89