Add description attribute to enum and read this description in TypeScript

Another interesting solution found here is using ES6 Map:

export enum Sample {
  V,
  IV,
  III
}

export const SampleLabel = new Map<number, string>([
  [Sample.V, 'FIVE'],
  [Sample.IV, 'FOUR'],
  [Sample.III, 'THREE']
]);

USE

console.log(SampleLabel.get(Sample.IV)); // FOUR

SampleLabel.forEach((label, value) => {
  console.log(label, value);
});

// FIVE 0
// FOUR 1
// THREE 2  

A variation of the accepted answer. Replace number with your enum type to be more type safe.

 export enum Sample {
      V,
      IV,
      III
    }

export const SampleLabel = new Map<Sample, string>([
  [Sample.V, 'FIVE'],
  [Sample.IV, 'FOUR'],
  [Sample.III, 'THREE']
]);

TypeScript doesn't allow you to add properties to an enum element, which at runtime is just a primitive string or number. Instead you have to do something like create a new type which holds references to these elements and also has the methods or properties you want.

Here's one possible way to do it. Start with your plain enum like this:

enum SampleEnum {
  V, IV, III
}

And let's give an interface definition for your extended type. It has a name, a description, and a number. Note that this type is generic so we can narrow down the name and number types shortly:

interface ISample<N extends number, S extends string> {
  readonly name: S;
  readonly description: string;
  readonly number: N;
}

Here is a function which can take your SampleEnum object and return something with the same keys but with values that implement the extended interface:

function makeSample<E extends Record<Extract<keyof E, string>, number>>(
  mapping: E
): { [K in Extract<keyof E, string>]: ISample<E[K], K> } {
  const ret = {} as { [K in Extract<keyof E, string>]: ISample<E[K], K> };
  (Object.keys(mapping).filter(k => k !== (+k) + "") as
    (Extract<keyof E, string>)[]
  ).forEach(k => {
    ret[k] = {
      name: k,
      description: "Blah " + k,
      number: mapping[k]
    }
  });
  return ret;
}

That might be a lot of type juggling, but it's basically just extracting the string-valued keys from SampleEnum (and ignoring the reverse mapping that adds numeric keys to numeric enums at runtime) and building an instance of the extended interface for each one, in a somewhat type-safe way.

Finally, let's create the Sample value and type which stands in for our enum:

const Sample = makeSample(SampleEnum);
type Sample = (typeof Sample)[keyof typeof Sample]

Okay, let's use it:

const nameOfIV = Sample.IV.name; // "IV"
console.log(nameOfIV); // "IV"
const numberOfIII = Sample.III.number; // SampleEnum.III
console.log(numberOfIII); // 1
const descriptionOfV = Sample.V.description; // string
console.log(descriptionOfV); // "Blah V"

const goodSample: Sample = Sample.III; // okay
const badSample: Sample = {
  name: "II", 
  description: "oops", 
  number: 3
}; // error, name doesn't match

Looks reasonable to me. See for yourself. Of course there are other ways to approach it but this should hopefully give you an idea. Hope that helps. Good luck!


Below method will be type safe (auto completion works) and it uses plain object. It is based on this feature of TypeScript.

export enum Sample {
  I = 1,
  II = 2,
  III = 3
}

export const SampleLabel: { [key in Sample]: string } = {
  [Sample.I]: "ONE",
  [Sample.II]: "TWO",
  [Sample.III]: "THREE",
};