30

Is it possible to have a condition inside of an interface declaration in TypeScript. What I'm looking for is a way to say, based on the value of the first key, the second key can be these values.

Example (non functioning):

interface getSublistValue {

    /** The internal ID of the sublist. */
    sublistId: 'item' | 'partners';

    /** The internal ID of a sublist field. */
    if (this.sublistId === 'item') {
        fieldId: 'itemname' | 'quantity';
    }

    if (this.sublistId === 'partners') {
        fieldId: 'partnername' | 'location';
    }
}

5 Answers 5

25

No there's not. The best thing to do is to create separate interfaces that describe the two different types of data.

For example:

interface SublistItem {
    sublistId: 'item';
    fieldId: 'itemname' | 'quantity';
}

interface SublistPartners {
    sublistId: 'partners';
    fieldId: 'partnername' | 'location';
}

function getData(): SublistItem | SublistPartners {
    return (Math.random() < 0.5)
        ? { sublistId: 'item', fieldId: 'itemname' }
        : { sublistId: 'partners', fieldId: 'partnername' };
}

const someValue = getData();

if (someValue.sublistId === "item") {
    // SublistItem in here
}
else {
    // SublistPartners in here
}
Sign up to request clarification or add additional context in comments.

2 Comments

So if I use this method when I'm creating the someValue object I still get all 4 options for fieldId with autocomplete, but at least it gives me a warning if the sublistId and fieldId don't match (i.e. 'item' and 'partnername'). The thing that gets me is that if my someValue is const someValue: SublistItem | SublistPartners = { sublistId: 'partners', fieldId: 'location' }; then I get a warning on the if (someValue.sublistId === 'item') {} condition saying that location is not itemname or quantity, I already know that, why would I get that warning?
@JonLamb hmmm... the error message displayed for that isn't helpful. In that case, it knows that sublistId will only be 'partners' and so it throws an error when you check for 'item'. If you replace 'item' with 'partners' then it will work. Try a scenario where you are receiving a value from somewhere and you're not sure what the data will be. I've updated the example.
16

If you would like to completely separate out your interfaces you could do something like this:

eg. sublist.model.ts

interface SublistItem {
    sublistId: 'item';
    fieldId: 'itemname' | 'quantity';
}

interface SublistPartners {
    sublistId: 'partners';
    fieldId: 'partnername' | 'location';
}

export type Sublist = SublistItem | SublistPartners;

Now you can use the Sublist type and it will correctly infer whether it is of type SublistPartners or SublistItem depending on the field values you provide.

Comments

4

You can try to solve this problem using conditional types:

interface SublistItem {
    sublistId: 'item';
    fieldId: 'itemname' | 'quantity';
}

interface SublistPartners {
    sublistId: 'partners';
    fieldId: 'partnername' | 'location';
}

type ConditionalSublist<bIsSublistItem=true> = bIsSublistItem extends true ? SublistItem : SublistPartners;

let sublistItem: ConditionalSublist<true> = {
    fieldId: "itemname",
    sublistId: "item"
};

let sublistPartner: ConditionalSublist<false> = {
    fieldId: "partnername",
    sublistId: "partners"
};

Comments

1

For interfaces unfortunately not but for types Yes there is and its fairly clean

type getSublistValue = {
     sublistId: 'item';
     fieldId: 'itemName' | 'quantity'; 
 } & {
     sublistId:'partners';
    fieldId: 'partnerName' | 'location';
}

Here is a great video explaining how to do it clean with types

Comments

0

You may use my example. After providing required props, typescript will suggest other props.

interface _ButtonProps {
  variant: 'flat' | 'primary';
}

interface FlatButton extends _ButtonProps {
  variant: 'flat';
  textColor?: string;
}

interface PrimaryButton extends _ButtonProps {
  variant: 'primary';
  backgroundColor?: string;
}

export type ButtonProps = FlatButton | PrimaryButton;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.