Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set required attributes in TypeScript

What is a proper way in TypeScript to mark some attributes as required? Let's say we have certain object, that must contain id field, but can also have as many other fields as it wants:

interface SomeType {
    id: number;
}

let obj: SomeType = { name: "OBJ" };           // GOOD ERROR! We must have `id`
let obj: SomeType = { id: 123 };               // OK
let obj: SomeType = { id: 123, name: "OBJ" };  // BAD ERROR! I just want to add a name

I do know that you can make optional attributes with a question mark, like this:

interface SomeType {
    id: number;
    name?: string;
    engine?: string;
    mother?: string;
}

But I don't want each time modify this interface by adding and mixing unrelated things to it.

Another option is of course to create a swarm of interfaces each extending the main one, but that sounds like a lot of lines of code:

interface SomeType {
    id: number;
}

interface FirstType extends SomeType {
    name: string;
}

At first I tried doing this:

interface SomeType extends any {
    id: number;
}

But TypeScript can't find name any. Maybe there is something like this with an exclamation point?

interface SomeType {
    id!: number;
}

UPDATE

I am using this type of interface when creating Redux actions, which must have property type and any number of other additional attributes. There are really a lot of actions, so I was reluctant to use any as a type.

like image 381
mseimys Avatar asked Jan 15 '16 12:01

mseimys


2 Answers

You can use an indexer for that:

interface SomeType {
    id: number;
    [key: string]: any;
}

let obj1: SomeType = { name: "OBJ" };           // GOOD ERROR! We must have `id`
let obj2: SomeType = { id: 123 };               // OK
let obj3: SomeType = { id: 123, name: "OBJ" };  // OK

[Playground]

However, please note that even the following code (i.e. with a non-string property key) is valid with the indexer above:

let obj4: SomeType = { id: 123, 1: "OBJ" }; // also OK

which confuses people but it is as it is.

like image 80
Martin Vseticka Avatar answered Oct 07 '22 01:10

Martin Vseticka


But I don't want each time modify this interface by adding and mixing unrelated things to it.

Don't add to or mix unrelated things in the same interface. Create separate interfaces and then combine them when necessary by using intersection types.

interface FirstType {
    id: number;
}

interface SecondType {
    name: string;
}

let myIntersectionObj: FirstType & SecondType = {
    id: 2,
    name: "some string"
};

That's useful in situations like this:

function myFirstFunction(obj: FirstType) { /* does something */ }
function mySecondFunction(obj: SecondType) { /* does something */ }

function myFunction(obj: FirstType & SecondType) {
    myFirstFunction(obj);
    mySecondFunction(obj);
}

myFunction(myIntersectionObj);

Using intersection types helps to make sure everything is typed in the application, which will help prevent mistakes. Indexed types should ideally only be used for key value objects.

like image 34
David Sherret Avatar answered Oct 07 '22 01:10

David Sherret