[FIXED] Typescript – interface extending another interface with nested properties

Issue

I have an interface like:

export interface Module {
  name: string;
  data: any;
  structure: {
    icon: string;
    label: string;
    ...
  }
}

How can I extend it, so that I add some new properties to structure, without repeating myself? Do I have to make a new interface for structure and extend that and make a new Module with the new structure interface or is there some other syntax to achieve this?

Ideally, I would just write something like:

export interface DataModule extends Module {
  structure: {
    visible: boolean;
  }
}

export interface UrlModule extends Module {
  structure: {
    url: string;
  }
}

Thanks!

Solution

Interfaces can’t add to the types of members in the base interface (at least not directly). You can use an intersection type instead:

export interface Module {
    name: string;
    data: any;
    structure: {
        icon: string;
        label: string;
    }
}

export type DataModule = Module & {
    structure: {
        visible: boolean;
    }
}

export type UrlModule = Module & {
    structure: {
        url: string;
    }
}

let urlModule: UrlModule = {
    name: "",
    data: {},
    structure: {
        icon: '',
        label: '',
        url: ''
    }
}

They should behave similarly to interfaces, they can be implemented by classes and they will get checked when you assign object literals to them.

You can also do it with interfaces but it’s a bit more verbose, and implies using a type query to get the original type of the field, and again an intersection:

export interface DataModule extends Module {
    structure: Module['structure'] & {
        visible: boolean;
    }
}

export interface UrlModule extends Module {
    structure: Module['structure'] & {
        url: string;
    }
}

The really verbose option (although in some ways simpler to understand) is of course to just define a separate interface for structure:

export interface IModuleStructure {        
    icon: string;
    label: string;
}
export interface Module {
    name: string;
    data: any;
    structure: IModuleStructure;
}
export interface IDataModuleStructure extends IModuleStructure {
    visible: boolean;
}
export interface DataModule extends Module {
    structure: IDataModuleStructure;
}
export interface IUrlModuleStructure extends IModuleStructure {
    url: string;
}
export interface UrlModule extends Module {
    structure: IUrlModuleStructure;
}
let urlModule: UrlModule = {
    name: "",
    data: {},
    structure: {
        icon: '',
        label: '',
        url: ''
    }
}

Edit

As pe @jcalz suggestion, we could also make the module interface generic, and pass in the apropriate structure interface:

export interface IModuleStructure {        
    icon: string;
    label: string;
}
export interface Module<T extends IModuleStructure = IModuleStructure> {
    name: string;
    data: any;
    structure: T;
}
export interface IDataModuleStructure extends IModuleStructure {
    visible: boolean;
}
export interface DataModule extends Module<IDataModuleStructure> {
}
export interface IUrlModuleStructure extends IModuleStructure {
    url: string;
}
export interface UrlModule extends Module<IUrlModuleStructure> {
}
let urlModule: UrlModule = { // We could also just use Module<IUrlModuleStructure>
    name: "",
    data: {},
    structure: {
        icon: '',
        label: '',
        url: ''
    }
}

Answered By – Titian Cernicova-Dragomir

Answer Checked By – David Marino (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published