67

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!

1 Answer 1

89

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: ''
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

What about using generics for Module? Or is that too complicated a suggestion?
Yeah .. that would work as well.. I'll add it when I get back to a PC :)
@KaloyanDimitrov let me know which version you decided to go with, I am curios which you found easier to implement/understand/write :)
structure: Module['structure'] & { visible: boolean; }; Is the syntax I was looking for.
So, what you are saying @TitianCernicova-Dragomir is that there is no way to do it? (for those who can't read it was a joke, yes I know he listed 4 ways to do it). I am going to use technique number 2 Module['structure'] & { visible: boolean; };

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.