As I have seen, there is no native nameof
-keyword like C# has built into TypeScript . However, for the same reasons this exists in C#, I want to be able to refer to property names in a type safe manner.
This is especially useful in TypeScript when using jQuery plugins (Bootstrap-Tagsinput) or other libraries where the name of a property needs to be configured.
It could look like:
const name: string = nameof(Console.log); // 'name' is now equal to "log"
The assignment of name
should change too when Console.log
got refactored and renamed.
What is the closest possible way of using such a feature in TypeScript as of now?
ts-nameof is a library that provides the exact functionality as C# does. With this you can do: nameof(console); // => "console" nameof(console.
C# NameOf operator is used to get name of a variable, class or method. It returns a simple string as a result. In error prone code, it is useful to capture a method name, in which error occurred. We can use it for logging, validating parameters, checking events etc.
As you have already said, there is no built in functionality on TypeScript as of version 2.8. However, there are ways to get the same result:
ts-nameof is a library that provides the exact functionality as C# does. With this you can do:
nameof(console); // => "console" nameof(console.log); // => "log" nameof<MyInterface>(); // => "MyInterface" nameof<MyNamespace.MyInnerInterface>(); // => "MyInnerInterface"
ts-simple-nameof offers an alternative. It basically parses a stringified lambda to figure out the property name:
nameof<Comment>(c => c.user); // => "user" nameof<Comment>(c => c.user.posts); // => "user.posts"
You can easily define your own nameof
that adds the type checking, however it will not refactor automatically as you'll still need to type a string literal:
const nameof = <T>(name: keyof T) => name;
It will return the passed property name but will generate a compile time error when the property name does not exist on type T
. Use it like so:
interface Person { firstName: string; lastName: string; } const personName1 = nameof<Person>("firstName"); // => "firstName" const personName2 = nameof<Person>("noName"); // => compile time error
Credits and more information about this
The type keyof T
now not only resolves to a string, but to string | number | symbol
(ref). If you still want to resolve strings only, use this implementation instead:
const nameof = <T>(name: Extract<keyof T, string>): string => name;
I think we often need more: to get class property names at runtime with compile-time validation. Renaming property will change nameOf expression. This is a really useful feature:
export type valueOf<T> = T[keyof T]; export function nameOf<T, V extends T[keyof T]>(f: (x: T) => V): valueOf<{ [K in keyof T]: T[K] extends V ? K : never }>; export function nameOf(f: (x: any) => any): keyof any { var p = new Proxy({}, { get: (target, key) => key }) return f(p); }
Usage example (no strings!):
if (update.key !== nameOf((_: SomeClass) => _.someProperty)) { // ... }
Example with existing instance:
export interface I_$<T> { nameOf<V extends T[keyof T]>(f: (x: T) => V): valueOf<{ [K in keyof T]: T[K] extends V ? K : never }>; } export function _$<T>(obj: T) { return { nameOf: (f: (x: any) => any) => { return nameOf(f); } } as I_$<T>; }
Usage:
let obj: SomeClass = ...; _$(obj).nameOf(x => x.someProperty); or _$<SomeClass>().nameOf(x => x.someProperty);
resolved to 'someProperty'.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With