DEV Community

Moses Karunia
Moses Karunia

Posted on • Updated on

Enum in typescript

Changelog:

  • 6 Nov 2019: Added type for better developer experience.

I know we can use typescript's enum, but, the difficulty arises when I'm receiving string in REST, and want to validate the property value in the request.

This is the way I write my enums:

// ===== DECLARATION =====

type TerrainEnum = 'MEADOW' | 'OCEAN' | 'FOREST';

class Terrain {
  public static readonly Meadow = 'MEADOW';
  public static readonly Ocean = 'OCEAN';
  public static readonly Forest = 'FOREST';

  public static isValid = (value: string): boolean => (
    value === Terrain.Meadow ||
    value === Terrain.Ocean ||
    value === Terrain.Forest
  );
}

// ===== USAGE =====

// Validate
Terrain.isValid(value);

// In function parameter
const myFunction = (terrain: TerrainEnum): void => {}

Enter fullscreen mode Exit fullscreen mode

I think this way you have a built-in input validations, simply by calling isValid().

I'm interested in what you guys think. Maybe we can find any better option out there.

So, what's your preferred way to write enums? And what's the reason behind yours?

Top comments (8)

Collapse
 
seangwright profile image
Sean G. Wright

How about creating a "Value Object" (often used in DDD).

class Terrain {
    public static readonly Meadow = new Terrain('meadow');
    public static readonly Ocean = new Terrain('ocean');
    public static readonly Forest = new Terrain('forest');
    public static readonly Unknown = new Terrain('unknown');

    public static readonly All = [
        Terrain.Meadow, 
        Terrain.Ocean, 
        Terrain.Forest
    ];

    private name: string;

    get Name() {
        return this.name;
    };

    protected constructor(name: string) {
        this.name = name;
    }

    find(name: string): Terrain | undefined {
        return Terrain.All.find(t => t.name === name);
    }

    isValid(name: string): boolean {
        return Terrain.All.some(t => t.name === name);
    }

    toString() {
        return this.name;
    }
}
Collapse
 
moseskarunia profile image
Moses Karunia

Hello, thanks for your answer. By the way I never heard of DDD yet. Care to elaborate? :)

Collapse
 
seangwright profile image
Sean G. Wright • Edited

Here's a link to the definition of Domain Driven Design (DDD)

"Domain-driven design is predicated on the following goals:

  • placing the project's primary focus on the core domain and domain logic;
  • basing complex designs on a model of the domain;
  • initiating a creative collaboration between technical and domain experts to iteratively refine a conceptual model that addresses particular domain problems."

And here's an implementation of Value Objects in Typescript

Collapse
 
neriojnavea profile image
Nerio Navea • Edited

How about using typescriptlang.org/docs/handbook/e...

enum TerrainEnum {
    Meadow = 'MEADOW',
    Ocean = 'OCEAN',
    Forest = 'FOREST'
}

class Terrain {
  public static isValid = (value: string): boolean => (
    value === TerrainEnum.Meadow ||
    value === TerrainEnum.Ocean ||
    value === TerrainEnum.Forest
  );
}

in that case, you can set in the constructor that you expect TerrainEnum value e.g.

class Terrain {
  constructor(name: TerrainEnum) {
    this.name = name;
  }
}
Collapse
 
moseskarunia profile image
Moses Karunia

Hi thanks for your answer. I think I prefer to define it in 1 class only. Any other alternatives? :)

Collapse
 
kryz profile image
Kryz

What do you think about this code:

enum TerrainType {
    Meadow = 'MEADOW',
    Ocean = 'OCEAN',
    Forest = 'FOREST'
}

class Terrain {
    public static isValid(value: string): boolean {
        return (<any>Object).values(TerrainType).includes(value);
    }
}

const result = Terrain.isValid('OCEAN');
Collapse
 
moseskarunia profile image
Moses Karunia

What happens if we omit <any>? Isn't it should infer any by default?

I personally, prefers to write it in 1. Splitting into 1 enum and 1 class seems weird.

One of the problem is when we try to deserialize string to enum.

E.g. when getting string from rest call. When this happens, I think we should make our parameter as Terrain | string anyway right?

Collapse
 
kryz profile image
Kryz • Edited

If you remove any you will get an error. Actually any should be avoided.

Alternative solution is:

class Terrain {
    public static isValid(value: string): boolean {
        return Object.keys(TerrainType).includes(value);
    }
}

But it works in >= ES7. Keep in mind that key and value in this enum must be the same, for example OCEAN = "OCEAN"

I find enum/class much cleaner that creating many static const in the class. Why do you find it weird?
It's completely fine to extract possible values to an enum/object/array/dictionary:

First if I had to add a new option adding it to an enum would be much cleaner than modifying the class and having for example 10 static constants in the class + modifying IF condition.

Secondly maybe other parts of your application will use this enum if your project starts to grow. In this case I would even consider to put this enum into a separate file. All parts would import the enum and would not depend on the Terrain class.

when getting string from rest call...
I think you always get a string (or number or null) from the rest call.
You can create an enum var like that:

const valueFromRest: string = "OCEAN";
const value: TerrainType = (<any>TerrainType)[valueFromRest];