55

I have problem with defined types and checking if a value is contained in that type.

Here is my example:

these are the types:

export type Key = 'features' | 'special';

export type TabTypes = 'info' | 'features' | 'special' | 'stars';

when the user changes a tab, it sends a string value from Type of TabTypes.

activeTabChanged(event: TabTypes) {
    this.activeTab: TabTypes = event;
    // it won't let me set the key here because key has a different type 
    // but the send event can be contained in type Key
    // how can I check if the send event from type TabTypes is contained in type Key
    this.key: Key = event;
}

is there a typescript way to check if a sent value with a type can equal a value from a different type?

0

6 Answers 6

31

2019 Solution:

I had the same need and found an easier way to do that in another thread. In summary, what Patrick Roberts says in that link (updated with this question values) is:

Don't over-complicate it.

function isOfTypeTabs (keyInput: string): keyInput is TabTypes {
  return ['info', 'features', 'special', 'stars'].includes(keyInput);
}

See What does the `is` keyword do in typescript? for more information on why we don't just use a boolean return type.

Credits and full source here: https://stackoverflow.com/a/57065680/6080254

Sign up to request clarification or add additional context in comments.

1 Comment

This no longer seems to work, or my situation is just slightly nuanced. I'm trying to create an object who's property is defined as type T = <string vals> (so obj {prop: T}). The incoming value that I need to place on prop is some string that I need to ensure is in T. The is doesn't seem to narrow correctly, so it still thinks my keyInput (not actually a keyInput) is a boolean. stackblitz.com/edit/…
4

You could use a string enum.

export enum Keys = {
  Features = 'features',
  Special = 'special',
}

// Compare it
if (currentKey === Keys.Special) { console.log('Special key is set'); }

In order to check if your value is defined in the predefined Enum at all you can do:

if (currentKey in Keys) { console.log('valid key'); }

3 Comments

currentKey in Keys Won't work with string enums, only with numeric enums
As @MaorRefaeli says above the use of currentKey in Keys doesn't work at all. How this has upvotes at all is strange, because if you attempt this it won't work. I spent a good 15 minutes refactoring my code only for it to not work when I ran my code.
@KG23 currentKey in Keys does work, but with the keys of the enum (e.g. 'Features' and 'Special') rather than its values (e.g. 'features' and 'special'). But I agree that without this detail, the answer is incomplete.
4
const TabValues = ['info', 'features', 'special', 'stars'] as const;
const TabValuesObj = TabValues.reduce(
  (acc, next) => ({ ...acc, [next]: null }),
  {} as Record<string, null>,
);
export type TabType = typeof TabValues[number];
export function isTabType(value: string): value is TabType {
  return value in TabValuesObj;
}

Here is an option that should also allow for very quick validation, even with large numbers of options in the type.

Comments

2

This answer to a similar question might be useful. it doesn't exactly answer your question, but it shows a similar way to achieve the desired result.

In short, you can use array for inclusion checks, and type for type safety:

const keys = <const> ['features','special'];
export type Key = typeof keys[number];
const tabTypes = <const> ['info' ,'features' ,'special', 'stars'];
export type TabTypes = typeof tabTypes[number];

activeTabChanged(event: TabTypes) {
    this.activeTab: TabTypes = event;
    // it won't let me set the key here because key has a different type 
    // but the send event can be contained in type Key
    // how can I check if the send event from type TabTypes is contained in type Key

    if (event in keys) {
        this.key: Key = event as Key;
    }
}

Comments

0

most of the answer here assume you know what the union type is before hand so i will just cover the edge case wher the type is being determined Dynamically

export type DataKeysId = keyof typeof data;
export const Data: Record<DataKeysId , string> = data;

where data is a json file that has been imported in the form of { a:"123", b:"123", c:"123" }

so by running keyof typeof data we get a dynamic union type of "a"|"b"|"c"

however all other methods here would require the creation of an array ["a","b","c"] which would not be updated if you added d:"456" to the json file

a simple fix for this is to use Object.keys(data) to generate your dynamic value list to go with your dynamic type

Object.keys(data).include("d")

Comments

-1

While Array.includes works, it has linear time complexity. Assuming that your list of tab types is unique (using a union type with repeated values wouldn't really make sense), it's faster to use a Set

const tabsSet = new Set(['info', 'features', 'special', 'stars'])
const isOfTypeTabs = (keyInput: string): boolean => tabsSet.has(keyInput)

console.log(isOfTypeTabs('info')) // true
console.log(isOfTypeTabs('foo')) /

1 Comment

This is classical example of premature optimization. Array is actually faster, unless you have large number of elements in the array. This is because cache locality out weights linear time complexity.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.