0

How I should to annotate parameter type as "any value of object" ?

class ExampleClass {

    private static readonly MODES = {
        DEVELOPMENT: 0,
        PRODUCTION: 1,
        TEST: 2
    }

    //  Any Value of ExampleClass.MODES
    constructor(mode: MODE[?]) {

    }
}

In this case, values 0, 1, 2 are meaningless if to use enum, but as far as I know, we cannot use enum as class field. So let's consider the some value of object case in this question.

2 Answers 2

5

Tomas was very close, but let's go ahead and give the full answer. To get the type of values of typeof MODES, just index it by the type of all of its keys.

type ValueOf<T> = T[keyof T];

// Prevent widening of the types of the constants to `number`.
function asLiterals<T extends number, U extends { [n: string]: T }>(arg: U) {
    return arg;
}

class ExampleClass {

    private static readonly MODES = asLiterals({
        DEVELOPMENT: 0,
        PRODUCTION: 1,
        TEST: 2
    });

    //  Any Value of ExampleClass.MODES
    constructor(mode: ValueOf<typeof ExampleClass.MODES>) {

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

6 Comments

This is the most valuable knowledge, cleared from the husk. Thank you for the answer!
@Matt, where did you figure out this construct T[keyof T]? It's marvelous!
@Tomas I must have seen it somewhere, but I don't remember where; it's been too long.
Just for you information, Matt proposed solution can be somehow hacked by square bracket notation member access i.e let a = new ExampleClass(ExampleClass.MODES['whoops']) will not detect types/values inconsistencies and will pass undefined to constructor. Just checked that in TS playground :) Small detail but worth knowing and one more good reason to restrict square brackets notations.
@Tomas Assuming noImplicitAny is enabled, your example gives me an error, Element implicitly has an 'any' type because type '{ DEVELOPMENT: number; PRODUCTION: number; TEST: number; }' has no index signature. But you did clue me into a related problem: my code would accept numbers other than the ones in MODES. I've fixed that.
|
1

you can use in this practicular case type:

type myType = 'DEVELOPMENT' | 'PRODUCTION' | 'TEST'
class someClass {
  //  Any Value of ExampleClass.MODES
  constructor(mode: myType ) { }    
}

For more advanced scenarios check below code snippet form TypeScript documentation, where K extends keyof T is used to ensure, that only values being part of object specification are passed as function arguments:

function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
  return names.map(n => o[n]);
}

interface Person {
    name: string;
    age: number;
}
let person: Person = {
    name: 'Jarid',
    age: 35
};
let strings: string[] = pluck(person, ['name']); // ok, string[]

Above can be part of your utility service and used universally across multiple constructors you wish to ensure types.

or even enclose values you wish to secure in separate class and use keyof directly:

class A  {
    prop1: 1;
    prop2: 2;
}

class TestClass {
    constructor(key: keyof A) {

    }
}

let tc1 = new TestClass('prop1')
let tc2 = new TestClass('whoops') // Error

And as far as I understand your intention, you wish to have something more like valueof then keyof. If so, then yes, enums and types are things you should focus IMHO.

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.