Typescript optionally extending interface

JavascriptTypescriptInterface

Javascript Problem Overview


I have two interfaces, one of which extends the other. However, I would like to be able to extend the first interface and make all of its types optional. I don't want to have to rewrite all of the definitions of the first interface to be optional in my second interface (because what's the advantage of extending at that point?) or redefine the first interface because it is being used elsewhere.

What it looks like:

interface First {
  type1: string
  type2: string
}

// Seemingly pointless rewrite (why would I even need to extend?)
interface Second extends First {
  type1?: string
  type2?: string
  type3: string
  type4: string
}

// What I imagine the extending should be (but doesn't work)
interface Second extends First? {
  type3: string
  type4: string
}

I did my research and did find this question that answers something very similar, but it's been a year since that question has been touched and I think my problem is not exactly the same because I want to make the entire extended interface optional, not just a few types from it.

Is there any way to do this in Typescript, or do I just need to suck it up and make a long second interface?


Update (to explain why I'd like to have this work):

I am writing a React web app and have a component that displays an entity from my database in a way that allows the user to edit any value of that entity. I would like my React component to handle the case where the user is creating a new entity, as well as the case where the user is editing an existing entity.

To keep with my above example, let's say that my database entity's values are replicated by the First interface and the React component uses two passed props that exist in the Second interface. The React component will always have the two values in the Second, but not necessarily have the values of the First.

In the case of the user creating a new entity, I'd like to construct the React component with only the values of Second, without having to specify null values for everything in First. In the case of the user editing an existing entity, I would pass everything from First and Second.

In both cases, it would be the same UI, but constructed with a different set of values.

Javascript Solutions


Solution 1 - Javascript

You can use type aliases along with an intersection on the Partial type:

type First = {
    type1: string;
    type2: string;
}

type Second = Partial<First> & {
    type3: string;
    type4: string;
}

Solution 2 - Javascript

You can do this with interfaces using the Partial type.

interface First {
    type1: string;
    type2: string;
}

interface Second extends Partial<First> {
    type3: string;
    type4: string;
}

Solution 3 - Javascript

You can also make all parts optional by providing an otherwise empty interface:

export interface ISingleScope {
   scope: string;
}

export interface IMultiScope {
   scopes: string[];
   check?: boolean;
}

export interface IProvidedScope 
   extends Partial<ISingleScope>, Partial<IMultiScope> { 
}

However, usually this will require an explicit test of the used property exists, as at runtime neither of these information is present. So if your object comes with the name options than this would be sufficient:

if (options && options.scopes) {
   // access properly 'IMultiScope'
}

Solution 4 - Javascript

There is a better/simpler method. Using Omit you can redefine only the specific named properties.

interface First {
    type1: string;
    type2: string;
}

interface Second extends Omit<First, "type1"> {
    type1?: string;
}

Solution 5 - Javascript

Extends in Typescript interface means that the second object will inherit what ever the first object has, if the properties of first one are optional or not they will be applied to the second without changes. You cannot change this behaviour in Typescript. The answer to you question you should not use extends for your case.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLouie BertoncinView Question on Stackoverflow
Solution 1 - JavascriptNitzan TomerView Answer on Stackoverflow
Solution 2 - JavascriptDibyo MajumdarView Answer on Stackoverflow
Solution 3 - JavascriptJoerg KrauseView Answer on Stackoverflow
Solution 4 - JavascriptSam SmithView Answer on Stackoverflow
Solution 5 - JavascriptPRAISERView Answer on Stackoverflow