Index signatures
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 type
Update (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}whereKeysandTypeare 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.
Answer from Akash on Stack OverflowHow do I dynamically assign properties to an object in TypeScript? - Stack Overflow
how to dynamically add a property to an object in typescript?
How can I programmatically add property to object in TypeScript? - Ask a Question - TestMu AI (formerly LambdaTest) Community
properties - how do you add a property to an existing type in TypeScript? - Stack Overflow
EDIT: I just realized I could just deep clone the object and alter that one instead.
Heres a simplified example of what I'm trying to do:
const foo = (apiReponse: {A: number, B: number) => {
return apiReponse.D = 123
}Basically I am pass in an api response and using the type thats generated from codegen (apollo graphql). However I want to add a new field to it dynamically, however its throwing an error for the obvious reason.
How do I get around this or this just one big anti pattern?
Index signatures
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 type
Update (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}whereKeysandTypeare 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.
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.
How can I dynamically add a property to an object in typescript? Here's a sample method:
processSearchResults(responseObject)
{
var blogPostSearchResults = Object.assign(new GetBlogPostsResponse(), responseObject);
this.blogPostSearchResults = blogPostSearchResults.CollectionResults;
var authorList = ['John Smith', 'Bill Jones'];
//append author list to each result
for (var blogPost in this.blogPostSearchResults)
{
blogPost.AuthorList = authorList ;
}}
In the example above, I'm taking an object with a particular definition, and then trying to create and assign a new property dynamically to provide an additional structure for html template view binding.
Intersection Type
In typescript, If you want to add members, you can use an intersection type:
type DateWithNewMember = T & { newMember: boolean }
Where T is the type you want to add member to.
Then use like this:
dates: DateWithNewMember<Date>[];
Union Type
You could use Union type:
class newDateClass {
readonly fullYearUTC: number;
}
Then use like this:
date: Date | newDateClass
You can not create a class named Date, you can have your own date object which extends it:
class MyDate extends Date {
get fullYearUTC(): number {
return this.getUTCFullYear();
}
}
But if you want to modify the existing Date you need to keep doing what you did with your javascript code.
As for adding it to the ts type, you need to use it as an interface:
interface Date {
readonly fullYearUTC: number;
}
Or you can augment the global namespace:
declare global {
interface Date {
readonly fullYearUTC: number;
}
}
What you are looking for is intersection type:
https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html
Just declare your custom type like following:
interface DefaultSession {
user?: {
name?: string | null;
email?: string | null;
image?: string | null;
};
expires: ISODateString;
}
type CustomDefaultSession = DefaultSession & {
user?: {
role?: string | null;
}
}
Then you can use it like it is or implement it for your class. Besides if you want the same name for your custom type then just refer library type using alias.
If you have a callback inside library you can override argument type like the following:
// Callback defined inside the library.
type LibraryCallback = (defaultSession: DefaultSession) => void;
// Function inside the library which accepts callback.
function libraryFunction(libraryCallback: LibraryCallback): void
{
// Do stuff...
}
// Application code which calls library function and changes callback argument type.
libraryFunction((customDefaultSession: CustomDefaultSession) =>
{
const role = customDefaultSession.user.role;
});
Just do the following:
export interface Foo {
user?: {
name?: string | null;
email?: string | null;
image?: string | null;
};
expires: Date;
}
export interface Bar extends Foo {
user?: Foo['user'] & {
role?: string
}
}
First, extend the Bar form Foo and assign the user just like above with & sign. & merges two object types. Thus you get:
