16

I don't think is possible but it's worth asking. In a generic method or class, I would like to log the name of the generic T type that has been used, I need it as string.

Imagine a method like this:

static getTypeName<T>(): string {
    return typeof T; // This is wrong
}

I would expect to be able to use it this way:

let m = getTypeName<Message>(); // Expected string "Message"
let p = getTypeName<Person>();  // Expected string "Person"

The above getTypeName is wrong, it won't even build. typeof would be transpiled in JS and wouldn't produce the desired effect. I tried various things but with no success.

Is there any way to actually do it?

EDIT: Why this is not a duplicate of: this question

The question and the example code shows how to get back an object and not the name of the type specified. Also the method is not generic because if you try to pass in a Type that has constructor parameters it will show the error: "[ts] Argument of type 'typeof MyType' is not assignable to parameter of type 'new () => MyType'." Moreover, if you use the function within a generic method you cannot use T as parameter, this means that everywhere I would need to get the name from the generic Type, I would need to add the ctor to the parameters.

Norcino
  • 5,850
  • 6
  • 25
  • 42
  • 2
    related: https://stackoverflow.com/questions/41160690/is-it-possible-to-get-the-type-name-of-a-generic-type – artem Nov 14 '17 at 16:46
  • 1
    Possible duplicate of [Is it possible to get the type name of a generic type?](https://stackoverflow.com/questions/41160690/is-it-possible-to-get-the-type-name-of-a-generic-type) – Duncan Nov 14 '17 at 16:48

3 Answers3

5

Well, Generics in TypeScript is just a construct that helps in typechecking. "T" is in fact "nothing" - it's nothing that can be transpiled into JavaScript. But you need this constructor thing to get your types name. That is why you get that error message. You provided just a type but required is a value (that has that constructor thing) to get your code to work.


Edit:

What you need to do in any case is to provide some JavaScript object. Something like this would work:

class Foo<T> {
    test: T;

    constructor(value: T) {
        this.test = value;
    }

    getTypeNameOfTest(): string {
        return this.test.constructor.name;
    }
}

const foo = new Foo<Date>(new Date());
foo.getTypeNameOfTest();                // => "Date"

const baa = new Foo<string>("Howdy");
baa.getTypeNameOfTest();                // => "string"

But here you provide a real JavaScript object instead of just a TypeScript type name. That's why this works.

Andreas
  • 1,551
  • 4
  • 24
  • 55
  • Andreas, given what you said, is there so a solution to do what I need? Or is it impossible? – Norcino Nov 29 '18 at 09:09
  • 1
    @Manuel: What you have asked for in your initial question is impossible. See my edited answer for details – Andreas Nov 29 '18 at 09:53
5

After searching all the internet, making my own decorator and fiddling with Typescript A LOT, I tripped over this post:

Get an object's class name at runtime

Basically, you can use Message.name as long as Message is a class.

Be careful, though, because if class names are minified when doing a production build, this can stop working, so check before using this approach.

Javi Marzán
  • 1,121
  • 16
  • 21
3

Using the answer here, I adapted it to better cover my needs, but still, this is not my full answer, because even dough it works, compiles and the tests passes, the following code shows an error in VS and VS Code.

The helper class:

export class TypeHelper {

    static typeName(ctor: { name:string }) : string {
        return ctor.name;
    }
}

The test class (Jasmine spec):

import { TypeHelper } from './../util/TypeHelper';

class MyClass {
    public MyGenericMethod<T>(): string {
        let iNeedTheTypeNameHere = TypeHelper.typeName(T);
        return `Use the string here somehow: ${iNeedTheTypeNameHere}`;
    }
}
describe("TypeHelper", () => {
    describe("TypeHelper", () => {
        it("typeName, returns the string type name of the Class passed as parameter", () => {
            let t = TypeHelper.typeName(TypeHelper);
            expect(t).toBe("TypeHelper");
            let m = TypeHelper.typeName(MyClass);
            expect(m).toBe("MyClass");
        });

        it("typeName, returns the string type name of a generic type T passed as parameter", () => {
            let myClass = new MyClass();
            let t = myClass.MyGenericMethod<TypeHelper>();         
            expect(t).toBe("TypeHelper");
            let m = myClass.MyGenericMethod<MyClass>();     
            expect(m).toBe("MyClass");
        });
    });
});

The error: [ts] 'T' only refers to a type, but is being used as a value here.

I'm not a TS expert but I will keep looking into this to try to understand how to solve this issue.

EDIT: Added playground example

Norcino
  • 5,850
  • 6
  • 25
  • 42