103

In TypeScript, I want to compare two variables containing enum values. Here's my minimal code example:

enum E {
  A,
  B
}

let e1: E = E.A
let e2: E = E.B

if (e1 === e2) {
  console.log("equal")
}

When compiling with tsc (v 2.0.3) I get the following error:

TS2365: Operator '===' cannot be applied to types 'E.A' and 'E.B'.

Same with ==, !== and !=. I tried adding the const keyword but that seems to have no effect. The TypeScript spec says the following:

4.19.3 The <, >, <=, >=, ==, !=, ===, and !== operators

These operators require one or both of the operand types to be assignable to the other. The result is always of the Boolean primitive type.

Which (I think) explains the error. But how can I get round it?

Side note
I'm using the Atom editor with atom-typescript, and I don't get any errors/warnings in my editor. But when I run tsc in the same directory I get the error above. I thought they were supposed to use the same tsconfig.json file, but apparently that's not the case.

Community
  • 1
  • 1
John J. Camilleri
  • 4,171
  • 5
  • 31
  • 41

9 Answers9

63

Well I think I found something that works:

if (e1.valueOf() === e2.valueOf()) {
  console.log("equal")
}

But I'm a bit surprised that this isn't mentioned anywhere in the documentation.

John J. Camilleri
  • 4,171
  • 5
  • 31
  • 41
31

There is another way: if you don't want generated javascript code to be affected in any way, you can use type cast:

let e1: E = E.A
let e2: E = E.B


if (e1 as E === e2 as E) {
  console.log("equal")
}

In general, this is caused by control-flow based type inference. With current typescript implementation, it's turned off whenever function call is involved, so you can also do this:

let id = a => a

let e1: E = id(E.A)
let e2: E = id(E.B)

if (e1 === e2) {
  console.log('equal');
}

The weird thing is, there is still no error if the id function is declared to return precisely the same type as its agument:

function id<T>(t: T): T { return t; }
artem
  • 46,476
  • 8
  • 74
  • 78
16

I would define values for Enum like this and compare with ===

const enum AnimalInfo {
Tiger = "Tiger",
Lion = "Lion"
}

let tigerStr = "Tiger";

if (tigerStr === AnimalInfo.Tiger) {
  console.log('true');
} else {
  console.log('false');
}
sendon1982
  • 9,982
  • 61
  • 44
  • 14
    Using `let tigerStr = "Tiger";` ruins enums concept. – Alexander Radchenko Jul 25 '19 at 17:25
  • 22
    Yes, but `tigerStr` may be loaded from a database, so comparing it to an enum value makes perfectly sense. – FabioLor Aug 24 '20 at 13:57
  • 1
    Agree with @FabioLor, and not only database case, there are several datasources that you have, that it's normal to compare one value with a enum. I don't know why there are more upvotes in the other comment than in this haha – Kalamarico Aug 03 '21 at 17:03
  • @AlexanderRadchenko comment is 100% valid. You should not compare a string with an enum. If you get a string from a data source parse it to an enum in the model and then use it. – Alex Aug 23 '23 at 04:33
13

If was able to compare two enums with this

 if (product.ProductType && 
       (product.ProductType.toString() == ProductTypes[ProductTypes.Merchandises])) {
      // yes this item is of merchandises
  } 

with ProductTypes being this export enum ProductTypes{Merchandises,Goods,...}

Bellash
  • 7,560
  • 6
  • 53
  • 86
13

Original AUG/18

In my case none of the above solutions worked, the reason was that I was casting the enum value to the enum object.

After that, I was trying to know if the enum was equivalent to another enum object... so I've created the following generic functions:

  public static enumEquals<T>(e: any, e1: T, e2: T): boolean {
    const v1 = this.enumValue(e, e1);
    return v1 === this.enumValue(e, e2, typeof v1);
  }

  private static enumValue<T>(enumType: any, value: T, validType?: string) {
    let v = enumType[value];
    if (!validType) {
      return v;
    }
    if (typeof v !== validType) {
      v = enumType[v];
    }
    if (typeof v !== validType) {
      v = enumType[v];
    }
    return v || null;
  }

This is an example of my test case:

enum SomeEnum {
  VALUE1, VALUE2, VALUE3, VALUE_DEF
}

const enumRefKey = localStorage.getItem('someKey');
const parsedEnum = SomeEnum[enumRefKey] || SomeEnum.VALUE_DEF;
console.log(parsedEnum);
if (parsedEnum === SomeEnum.VALUE_DEF) {
  // do stuff
}

That code didn't worked, after I've tried the solutions given here at this questions, I've found that when enumRefKey is valid console.log(parsedEnum) was printing numbers and the text VALUE_DEF when is not. The same result happend using all other solutions:

  • parsedEnum as SomeEnum
  • parsedEnum.valueOf()
  • SomeEnum[parsedEnum]

The solution using the generic methods looks like this:

enum SomeEnum {
  VALUE1, VALUE2, VALUE3, VALUE_DEF
}

const enumRefKey = localStorage.getItem('someKey');
const parsedEnum = SomeEnum[enumRefKey] || SomeEnum.VALUE_DEF;
console.log(parsedEnum);
if (this.enumEquals(SomeEnum, parsedEnum, SomeEnum.VALUE_DEF) {
  // do stuff
}

Update SEP/21

Best way to avoid all the issues related to enums in TypeScript comparison is to declare them like the following example.

Instead of this:

enum SomeEnum {
  VALUE1, VALUE2, VALUE3
}

Do this:

enum SomeEnum {
  VALUE1 = 'VALUE1', VALUE2 = 'VALUE2', VALUE3 = 'VALUE3'
}

This way from now you won't need to cast nor convert enum values to enum objects, and if you need to it'll always work. With this solution, all of the following examples are valid, and they'll return true:

console.log(SomeEnum['VALUE1'] === 'VALUE1');             // prints 'true'
console.log(SomeEnum['VALUE1'] === SomeEnum.VALUE1);      // prints 'true'
console.log(SomeEnum['VALUE1'] === 'VALUE1' as SomeEnum); // prints 'true'
console.log(SomeEnum['VALUE1'] === 'VALUE1');             // prints 'true'
console.log(SomeEnum['VALUE1'] === (<SomeEnum>'VALUE1')); // prints 'true'
console.log(SomeEnum.VALUE1 === 'VALUE1' as SomeEnum);    // prints 'true'
console.log(SomeEnum.VALUE1 === (<SomeEnum>'VALUE1'));    // prints 'true'
console.log(SomeEnum.VALUE1 === 'VALUE1');                // prints 'true'

Culprit

The reason for all this issues is that when TypeScript is compiled to JavaScript enums are parsed as objects like this

// this enum at TS
enum SomeEnum {
  VALUE1, VALUE2, VALUE3
}
// is parsed to JS like this:
{
  VALUE1: 1, VALUE2: 2, VALUE3: 3, 1: 'VALUE1', 2: 'VALUE2', 3: 'VALUE3'
} 

As you can see, once the enum is parsed to JS the reason for all the comparison issues becomes obvious, as we mistakenly may be comparing string vs number which may end up in false-positive results. The following is the second enum parsed to JS which works way better:

// this enum at TS
enum SomeEnum {
  VALUE1 = 'VALUE1', VALUE2 = 'VALUE2', VALUE3 = 'VALUE3'
}
// is parsed to JS like this:
{
  'VALUE1': 'VALUE1', 'VALUE2': 'VALUE2', 'VALUE3': 'VALUE3'
} 
luiscla27
  • 4,956
  • 37
  • 49
8

The only thing that worked for me (in typescript 2.2.1) was this:

if (E[e1] === E[e2]) {
  console.log("equal")
}

This compares the strings representing the names (eg. "A" and "B").

Russ
  • 623
  • 8
  • 14
5

Type casting enums to strings is a very valuable technique.

For example;

if (String(e1) === String(e2)) {
    console.log("equal, now actually works!")
}
Sydwell
  • 4,954
  • 1
  • 33
  • 36
3

In typescript an example enum:

enum Example {
   type1,
   type2
};

is transformed to javascript into this object:

Example {
    '0': 'type1', 'type1': 0,
    '1': 'type2', 'type2': 1
}

I had many problems with comparison enums in typescript. This simple script solves the problem:

enum Example {
    type1 = 'type1',
    type2 = 'type2'
};

then in javascript, the object is transformed into:

Example {
    'type1': 'type1',
    'type2': 'type2'
}

If you don't need to use enums - it's better not to use. Typescript has more advanced types, more here: https://www.typescriptlang.org/docs/handbook/advanced-types.html You can use instead:

type Example = 'type1' | 'type2';
Marek Woźniak
  • 1,766
  • 16
  • 34
  • Last example saved me. Seems this is the idiomatic TS way to implement enum like behavior all around. Still it's sad that TS still has to have these unsound... quirks? – Paul-Sebastian Manole Dec 03 '20 at 02:33
1

The error is thrown because the compiler realizes that the statement is always false and therefore redundant. You declare two variables which are clearly not equal and then try and see whether they are equal.

If you change it to e.g.:

enum E {
  A,
  B
}

foo() {
  let e1: E = E.A
  let e2: E
  e2 = bar();

  if (e1 === e2) {
    console.log("equal")
  }
}

bar(): E {
  return E.B
}

it should compile without an error.

On a sidenote, sth. like

let e1 = E.A;
if (e1 && e1 === E.B) {
  ...
}

would also not compile, as e1 in this case is 0 (as A is the first enum 'option') and therefore false which means that the second state would never be reached (disregarding whether the second statement would even be valid in this case)

seBaka28
  • 932
  • 1
  • 7
  • 16
  • I think `e2 = foo()`, should be `e2 = bar()`. I've tried to send this in as an edit, but it appears to be that more people have send those in. I apologize for digging up this old answer. – Wouter Verleur Jul 12 '21 at 11:22