5

I have a class property with a type that's the union of multiple string literals:

public reaction? : 'haha' | 'wow' | 'angry' | 'sad' | 'like';

What I'm trying to do was to have these strings defined somewhere else in some kind of array and then just use the array in the property definition. So something like:

allowedStrings = ['haha','wow','angry','sad','like'];
public reaction? : allowedStrings;

I get that the above is not possible but it's the general idea. Does typescript offer anything that does the job?

5 Answers 5

7

Since TypeScript 3.4 you can generate a type from runtime value using const assertions.

const allowedStrings = ['haha', 'wow', 'angry', 'sad', 'like'] as const;
type AllowedString = typeof allowedStrings[number]; // [number] is important here

// We can freely map over values
const mapped = allowedStrings.map((s) => s.toUpperCase());

// And use generated type to create type-safe functions
const process = (s: AllowedString) => {
  // Type of s is
  //    s: "haha" | "wow" | "angry" | "sad" | "like"
  return s;
};
Sign up to request clarification or add additional context in comments.

Comments

2

It depends on exactly how you intend to use it, but a string enum may give you what you are after:

enum ReactionKind {
    haha = 'haha',
    wow = 'wow',
    angry = 'angry',
    sad = 'sad',
    like = 'like'
}

const reaction: ReactionKind = ReactionKind.angry;

// Get the string value  
const stringValue = ReactionKind[reaction];

// Get the Enum from a string
const enumValue = ReactionKind['wow'];

You can still use the plain string values where you need them, but you get to use it as a type and as a runtime value, which seems to be what you are after.

You'll also notice that with string enums, if you use a string key when mapping to an enum, it will get checked... as long as you are using --noImplicitAny.

// Error
const enumValue = ReactionKind['wat'];

Comments

1

No, you can't define the valid values for a string in an array and have it be checked at compile time.

The reason for this becomes a little more apparent when you run your first example through the TypeScript compiler - it goes from this:

class Test {
    public reaction?: 'haha' | 'wow' | 'angry' | 'sad' | 'like' = 'haha';
}

To this:

var Test = (function () {
    function Test() {
        this.reaction = 'haha';
    }
    return Test;
}());

Your types aren't there any more once the compiler is done running! It's just JavaScript, with no additional logic added beyond what you explicitly wrote. You can't store the valid values in an array, as the contents of the array wouldn't get evaluated until the code is actually running, by which point the type checking has already been carried out and the types have been thrown away.

Therefore, if you wanted to check that the string matched a value from an array, you'd need to actually write some code that does that check at runtime.

Comments

1
enum Reaction {
    'haha',
    'wow',
    'angry',
    'sad',
    'like'
}

let reaction: keyof typeof Reaction;
reaction = 'angry'; // Fine
// reaction = 'whatever'; // Error

It appears the above should do what you want. If you still need an array of the strings, you can get it as follows:

const allowedStrings = Object.keys(Reaction).filter(k => Number.isNaN(+k));

Comments

1

You can use a type to name an union type.

type Reaction = 'haha' | 'wow' | 'angry' | 'sad' | 'like';
const reaction?: Reaction = 'haha';

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.