Typescript: How to convert a string to a type

Marek Krzeminski picture Marek Krzeminski · Jan 10, 2019 · Viewed 10.4k times · Source

I am creating a bunch of web components and each of them can emit custom events. As an example here are two simple examples:

//MyButton
emits 'myButtonPressed' and detail of this type:
interface ButtonDetailType {
  id: string;
}

//MySlider
emits 'mySliderChanging' and details of this type:
interface SliderChangingDetailType {
  id: string;
  value: number;
}
emits 'mySliderChanged' and details of this type:
interface SliderChangedDetailType {
  id: string;
  oldValue: number;
  newValue: number;
}

To listen to the components I have code that looks like:

buttonEl.addEventListener( 'myButtonPressed', ( event : CustomEvent ) => {
  const detail = event.detail as ButtonDetailType;
  //now i have access to detail.id
} );

sliderEl.addEventListener( 'mySliderChanging', ( event : CustomEvent ) => {
  const detail = event.detail as SliderChangingDetailType;
  //now i have access to detail.id, detail.value
} );

As I'm making more components, I'm having difficulty remembering all the custom event names that each component can emit, or the detailType that each event generates.

To solve this problem I was hoping to create an object that contains all the information like this:

EVENTS = {
  button: {
    myButtonPressed: 'myButtonPressed',
    detailType: 'ButtonDetailType',
  },
  slider: {
    mySliderChanged': 'mySliderChanged',
    detailTypes: {
     'SliderChangedDetailType',
     'SliderChangingDetailType',
    }
  }
};

With that, I now have an easy way to access all the Event names available for each component with auto-complete helping me along the way as I type:

buttonEl.addEventListener( EVENTS.button. //autocomplete shows me all event names here! YAY!
sliderEl.addEventListener( EVENTS.slider. //autocomplete here too!

The problem that I am having is I don't know how to convert a string to a type. I'd like to be able to type this:

buttonEl.addEventListener( EVENTS.button.myButtonPressed, ( event : CustomEvent ) => {
  const detail = event.detail as EVENTS.button.detailType; // <- invalid: EVENTS.button.detailType is a string not a type!
} );

Is there a way to convert strings to types in TypeScript?

Answer

jcalz picture jcalz · Jan 10, 2019

You can't really convert strings to types without a mapping. In fact, you really don't want those strings in the first place except for the actual string passed into addEventListener() as the type parameter. It seems like what you actually want is something like namespaces or modules to organize your types.

For example, using namespaces we can get something similar to your EVENTS object, except that instead of referring to just string values, it refers to string values and types:

namespace EVENTS {
  export namespace button {
    export const myButtonPressed = "myButtonPressed";
    export namespace detailType {
      export interface ButtonDetailType {
        id: string;
      }
    }
  }
  export namespace slider {
    export const mySliderChanged = "mySliderChanged";
    export namespace detailTypes {
      export interface SliderChangingDetailType {
        id: string;
        value: number;
      }
      export interface SliderChangedDetailType {
        id: string;
        oldValue: number;
        newValue: number;
      }
    }
  }
}

This should give you the same autocompletes you saw before, plus ones for the types:

buttonEl.addEventListener(EVENTS.button.myButtonPressed, ((event: CustomEvent) => {
  const detail = event.detail as EVENTS.button.detailType.ButtonDetailType;
}) as EventListener);

sliderEl.addEventListener(EVENTS.slider.mySliderChanged, ((event: CustomEvent) => {
  const detail = event.detail as EVENTS.slider.detailTypes.SliderChangedDetailType;
}) as EventListener)

It's up to you if you want to change the level of nesting and naming of things since the namespace makes some things redundant (maybe you want EVENTS.slider.details.SliderChanged instead of EVENTS.slider.detailTypes.SliderChangedDetailType), but the main idea here is the general approach of using namespaces or modules.

Hope that helps; good luck!