Extending type in typesctipt

Issue

There’s this type already available:

type GeometryFeature = {
    type: "Feature";
    properties: {
        id: string;
        refId: string | null;
        rootZoneId: string;
        name: string;
        description: string | null;
        startAt: string | null;
        endAt: string | null;
        createdAt: string;
        updatedAt: string;
    };
}

I would like to add a few more properties, let’s say these two: fill of type string, fillOpacity of type number by extending the GeometryFeature type. But I would not like to avoid modifying the existing code. The idea behind these two params is to add colour. So I guess, the new type that would include colour related props could be called GeometryFeatureColored.

How would you extend the GeometryFeature turning it into GeometryFeatureColored?

The GeometryFeatureColored is expected to look like this:

type GeometryFeatureColored = {
    type: "Feature";
    properties: {
        id: string;
        refId: string | null;
        rootZoneId: string;
        name: string;
        description: string | null;
        startAt: string | null;
        endAt: string | null;
        createdAt: string;
        updatedAt: string;
        fill: string;
        fillOpacity: number;
    };
}

Preferably, I’d like to know what is the least verbose way of defining this type.

Solution

Probably the least verbose way to write this would be something like

interface GeometryFeatureColored extends GeometryFeature {
  properties: GeometryFeature['properties'] & 
    { fill: number, fillOpacity: number }
}

Since your GeometryFeature interface’s properties member is of an anonymous type and is not a named interface, you need to use the indexed access type GeometryFeature['properties'] to refer to it. Then you can intersect it with the object type containing the properties you wish to add.

Let’s test that it works as expected:

declare const gfc: GeometryFeatureColored;
gfc.properties;
/* (property) GeometryFeatureColored.properties: {
  id: string;
  refId: string | null;
  rootZoneId: string;
  name: string;
  description: string | null;
  startAt: string | null;
  endAt: string | null;
  createdAt: string;
  updatedAt: string;
  } & {
  fill: number;
  fillOpacity: number;
} */
gfc.properties.fill.toFixed(); // okay
gfc.properties.name.toUpperCase(); // okay

Looks good.


Note that you could also write

type GeometryFeatureColored = GeometryFeature & {
  properties: { fill: number, fillOpacity: number }
};

which is even shorter, but intersecting at the top level is not completely identical to intersecting in a nested level. That is, {a: X & Y} is similar to but observably different from {a: X} & {a: Y}, and I believe you are asking for the former and not the latter. But I present it as an option in case terseness trumps all other concerns.


Personally I prefer to have named interfaces to refer to instead of intersections, since interfaces are more complex when viewed with IntelliSense:

type GeometryFeatureProperties = GeometryFeature['properties'];
interface GeometryFeatureColoredProperties extends GeometryFeatureProperties {
  fill: number, fillOpacity: number
}
interface GeometryFeatureColored extends GeometryFeature {
  properties: GeometryFeatureColoredProperties
}

As you can see, this is more verbose up-front, but is terser when you start using it (compare to the GeometryFeatureColored.properties IntelliSense info above):

declare const gfc: GeometryFeatureColored;
gfc.properties;
// (property) GeometryFeatureColored.properties: GeometryFeatureColoredProperties

Playground link to code

Answered By – jcalz

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published