2

I have several types of objects, like articles, divisions, profiles, etc. I defined an interface for each, basically:

interface IArticle {
    title: string;
    body: string;
}

interface IProfile {
    name: string;
    email: string;
}

interface IDivision {
    name: string;
    leader: IProfile;
}

Now I want to, in some cases, be able to add a formTitle property when using these on a page that displays a form. I thought I could do something like this:

// Failed
interface IForm<T> {
    formTitle: string;
}

function formDisplay(resource: IForm<IProfile>) { }

But when I do that, I get an error indicating the object properties (name and email, in this case) do not exist on type IForm<IProfile>. So I guess this is not the correct use of generics. Coming from Ruby and JavaScript, I'm still new to the whole static typing thing.

To get around this, I have been writing separate interfaces for each object, like this:

// Not reusable
interface IArticleForm extends IArticle {
    formTitle: string;
}

Another alternative I could think of would be to add an optional property to a base interface and then extend the regular object interfaces from there.

// Does not provide helpful type checking
interface IBase {
    formTitle?: string;
}
interface IArticle extends IBase { }

But I want formTitle to be required on these form pages so that I don't forget to set it. Is there some way to apply a group of required properties to multiple objects in a reusable way?

3 Answers 3

5

Looks like you are looking for Intersection Types. this allows you to mix to behaviors together. You can even alias the newly created type to give it a convenient name that describes its usage.

For your example use:

interface IProfile {
    name: string;
    email: string;
}
interface IForm {
    formTitle: string;
}
type IProfileForm = IForm & IProfile;

function formDisplay(resource: IProfileForm) { }
Sign up to request clarification or add additional context in comments.

4 Comments

intersections are crazy powerful
You'll need to remove the <T> on IForm. While this works, I personally would not recommend it unless you are converting old js - union and intersection types have their place (faking overloads and mixins where you are returning, not taking, an intersection), but they tend to complicate code when they are used to address simple issues that are routinely solved by generics. The term "crazy powerful" should be a warning flag
This will be very useful in this case, but I think mk.'s answer more directly answers the question.
Thank you for pointing out the extra <T> @mk. I personally disagree with you that union and intersection types do not have there place in regular Typescript . I think that the expressivity and power of union and intersection types gives Typescript a leg up over other strongly typed languages where the Type System quickly becomes too constraining. I updated my answer to use a type alias to further my point.
3

Generics are intended to "contain" whatever type is generic - you need a field in your IForm that is of your generic type:

interface IMyContent {}

interface IProfile extends IMyContent {
    name: string;
    email: string;
}

interface IForm<T extends IMyContent> {
    formTitle: string;
    content: T;
}

var x : IForm<IProfile> = {
    formTitle: "",
    content: {name: "", email: ""}
}

Comments

0

You should use Type inside the interface to have effect...


interface IObject<Type> {
  [x:string]: Type;
};

const stringObj:IObject<string> = {
  a: 'b',
  c: 1 // will give error
}

const anyObj:IObject<string|number> = {
  a: 'b',
  c: 1 // no error
}

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.