DEV Community

ben hultin
ben hultin

Posted on

1

TS: Next Level Index Signatures Inside Interfaces and Types

With Typescript we can make our interfaces flexible as to what kind of properties it will accept. Why would this be helpful you may ask, well there are various situations where we need to define the TS compiler to more open to other properties besides the ones defined in the interface. These would include but not limited to:

  • The interface will have more properties than is reasonable to define.
  • We intend to access the properties dynamically via bracket notation myObj[value].

So lets take a look how this can be done

interface Pet {
  // allows any property value of type string
  // what if we want more control over possible properties
  [key: string]: string
}

const myPet: Pet = {
  // all valid values
  // what if we have a general rule about our props naming convention?
  foo: 'foo',
  bar: 'bar',
  kbgyb: '??'
}
Enter fullscreen mode Exit fullscreen mode

This approach maybe a bit too flexible and we have a pattern our properties will look

interface Pet {
  // by adding the prefix 'content-' we can lock down part of the props name
  [key: `content-${string}`]: string
}

const myPet: Pet = {
  'content-type': 'foo', // valid property name
  two: 'bar' // invalid property name as it is not prefixed with 'content-'
}
Enter fullscreen mode Exit fullscreen mode

Let us take this another step further and define the different values our interface / type can accept.

type StringProps = 'type' | 'name' | 'breed';
type StringPet { [key in StringProps]: string }

// same as this 
interface StringPet {
  type: string;
  name: string;
  breed: string;
}

const myPet: Pet = {
  type: 'foo', // valid property name
  two: 'bar' // invalid property name
}
Enter fullscreen mode Exit fullscreen mode

By making use of StringProps we can reduce the interface from 5 lines of code to 2 also without having to write string 3 times as well. This approach can be expanded even further:

type StringProps = 'type' | 'name' | 'breed' | 'eyeColor';
type StringPet { [key in StringProps]: string }

type IntProps = 'age' | 'weight' | 'legs';
type IntPet { [key in IntProps]: number }

type BoolProps = 'wings' | 'claws' | 'fur';
type BoolPet { [key in BoolProps]: boolean }

// here we intersect two types together into one
// note: types are intersected, not extended like interfaces are
type WholePet = IntPet & StringPet & BoolPet;

// WholePet in the more verbose view looks like this
interface WholePet {
  type: string;
  name: string;
  breed: string;
  eyeColor: string;
  age: number;
  weight: number;
  legs: number;
  wings: boolean;
  claws: boolean;
  fur: boolean;
}
Enter fullscreen mode Exit fullscreen mode

The above approach not only brings control over index signature, but can also be used to make our interfaces and types much more scalable. We are not forced to repeat ourselves with primitive types like string, number, or boolean.

If we want to change the primitive type for one of our properties, we simply move it to appropriate Prop type like from IntProps to StringProps.

Thanks for reading!

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up