Conner Enders
Conner Enders

Reputation: 126

How can I find the corresponding type of a given key of T?

Apologies for the title, having trouble making it clearer.

Let's say I have a class Person:

class Person {
  name: string;
  age: number;
}

And another class FormControl:

class FormControl<T> {
  constructor(
    public key: keyof T,
    public value: any // What should value's type be?
  ) {}
}

What I'm trying to achieve is to create an instance of the FormControl class as such...

new FormControl<Person>('name', 'Bob');

Where the key property must be a key of T, and the value property must be the corresponding type of the given key of T.

Simply having keyof T works great for the key field, but I am not sure how to approach the type for the value field.

Is this possible with Typescript? Or is there another way to achieve something similar to this?

Upvotes: 1

Views: 67

Answers (2)

Conner Enders
Conner Enders

Reputation: 126

Thank you for the great answer superhawk610!

Rather than using that factory, I am using another class's constructor to group the FormControl instances together, which also removes the need to specify both generics:

class FormControlGroup<T> {
  constructor(
    public controls: FormControl<T, any>[]
  ) {}
}

Not sure if I should be using any as the second generic, but it seems to work.

FormControlGroup in use:

new FormControlGroup<Person>([
  new FormControl('name', 'Bob'),
  new FormControl('age', 'eleventeen') // TypeError - expected number
])

Upvotes: 0

superhawk610
superhawk610

Reputation: 2653

TypeScript has property access notation Index[Key] for retrieving the type of the value specified for the corresponding key in an index type:

type PersonName = Person['name']; // string
type PersonAge = Person['age']; // number

You can use this to constrain the constructor like so:

class FormControl<T, K extends keyof T> {
  constructor(
    public key: K,
    public value: T[K],
  ) {}
}

Unfortunately, TS doesn't allow you to specify only a subset of generic type parameters, so you'll have to specify both generics:

new FormControl<Person, 'name'>('name', 'Bob');
new FormControl<Person, 'age'>('age', 'five'); // TypeError - expected number

You can work around this using a curried function, but the syntax is a bit awkward:

const formControlFactory = <T extends any>() =>
  <K extends keyof T>(key: K, value: T[K]) =>
    new FormControl(key, value);

const personFCFactory = formControlFactory<Person>();
personFCFactory('name', 'Bob');
personFCFactory('age', 'five'); // TypeError - expected number

You can read the discussion on specifying only a subset of generic type parameters here.

Upvotes: 1

Related Questions