Get an object's class name at runtime
Asked Answered
T

11

457

Is it possible to get an object's class/type name at runtime using TypeScript?

class MyClass{}

var instance = new MyClass();
console.log(instance.????); // Should output "MyClass"
Tankard answered 28/11, 2012 at 20:7 Comment(3)
See here. At runtime you are running JavaScript.Adames
How do you get the constructor name in TypeScript file though? You can't do this.constructor.name in a TypeScript method (in .ts file).Ipswich
And what about the name of an interface?Drews
I
739

Simple answer :

class MyClass {}

const instance = new MyClass();

console.log(instance.constructor.name); // MyClass
console.log(MyClass.name);              // MyClass

However: beware that the name will likely be different when using minified code.

Incision answered 15/4, 2016 at 9:21 Comment(13)
Unfortunately MyClass.name is an ES6 feature hence it does not work in IE11.Jinks
typescript will throw error on this. should do let instance: any = this.constructor; console.log(instance.name); Orchestra
@Orchestra a terser way to avoid casting to any is console.log(instance.constructor['name']);Mammillate
@Orchestra You could also create a type-declaration instead: interface Function { name: string; } -- this will extend the "native" definition.Toccaratoccata
How about getting the name of a function? Like the C#'s nameOf ?Lynnelynnea
Doesn't work in TypeScript: "Property 'name' does not exist on type 'Function'." Nick's version works.Lark
Doesn't work out of the box. You need to cast to any first before you can use constructor.name same with @NickStrupat suggested solutionSelect
I think it should be noted that the two options are close but not quite equivalent. In case of subclassing, this.constructor.name will give you the child class name while the other one will obviously stay fixed to the parent.Cogitate
You can polyfill Function.name so that this strategy will work on more browsers: github.com/JamesMGreene/Function.nameChuck
MyClass.name won't work well if you are minifying your code. Because it will minify the name of the class.Giulia
Once minified, I found out that the constructor name just turns to "e". Any ways to solve this?Aliaalias
@Aliaalias not that I know of. Minification will really rename the class. Depending on what you are trying to achieve, using the constructor name at runtime might not be the best strategy.Incision
@MikaelCouzic yeah, its okay, but thanks for the info :)Aliaalias
L
44

My solution was not to rely on the class name. object.constructor.name works in theory. But if you're using TypeScript in something like Ionic, as soon as you go to production it's going to go up in flames because Ionic's production mode minifies the Javascript code. So the classes get named things like "a" and "e."

What I ended up doing was having a typeName class in all my objects that the constructor assigns the class name to. So:

export class Person {
id: number;
name: string;
typeName: string;

constructor() {
typeName = "Person";
}

Yes that wasn't what was asked, really. But using the constructor.name on something that might potentially get minified down the road is just begging for a headache.

Leija answered 24/4, 2017 at 21:12 Comment(4)
event after the code is minified , i'm fairly sure you can still do ` let instance=new Person (); (instance.constructor.name==Person.name) both names should get minified to the same thing.Ase
@ChristopherChase It's expected that both names will be minified to the same thing, but usually, it's gonna be something short and non-unique like a, b, c, etc. so you shouldn't rely on this.Swor
I think this is probably the correct answer for most people looking this up. I think it's important to be mindful of where Typescript "exists," and be weary of relying on it at runtime. It is very easy/tempting (at least for me) to use the classnames directly in the data, but ask yourself: at that point, is the data representing the class, or is the class representing the data? I'm not telling you it always has to be one way or the other, but I am saying you have to pick one and stick with it, or you will be chasing your tail.Greenbrier
This solution will be easier to implement across frontend and backend, since minification will not effect it.Greenbrier
L
31

I know I'm late to the party, but I find that this works too.

var constructorString: string = this.constructor.toString();
var className: string = constructorString.match(/\w+/g)[1]; 

Alternatively...

var className: string = this.constructor.toString().match(/\w+/g)[1];

The above code gets the entire constructor code as a string and applies a regex to get all 'words'. The first word should be 'function' and the second word should be the name of the class.

Hope this helps.

Lacreshalacrimal answered 9/6, 2015 at 6:49 Comment(1)
Sorry, sure. Usually, you use minification, uglyfication and other post processing systems. So on production server your class name will not be the same. And your code will not work. I didn't find really good solution to get class name. The most suitable way is to define a static variable with your class name.Capsulate
H
27

You need to first cast the instance to any because Function's type definition does not have a name property.

class MyClass {
  getName() {
    return (<any>this).constructor.name;
    // OR return (this as any).constructor.name;
  }
}

// From outside the class:
var className = (<any>new MyClass()).constructor.name;
// OR var className = (new MyClass() as any).constructor.name;
console.log(className); // Should output "MyClass"

// From inside the class:
var instance = new MyClass();
console.log(instance.getName()); // Should output "MyClass"

Update:

With TypeScript 2.4 (and potentially earlier) the code can be even cleaner:

class MyClass {
  getName() {
    return this.constructor.name;
  }
}

// From outside the class:
var className = (new MyClass).constructor.name;
console.log(className); // Should output "MyClass"

// From inside the class:
var instance = new MyClass();
console.log(instance.getName()); // Should output "MyClass"
Hire answered 30/7, 2016 at 0:20 Comment(2)
Tried on TypeScript 2.6.2 and I'm getting this error: Property 'name' does not exist on type 'Function'.Throughway
(this as {}).constructor.name or (this as object).constructor.name is better than any because then you actually get autocomplete :-)Nee
E
20

Solution using Decorators that survives minification/uglification

We use code generation to decorate our Entity classes with metadata like so:

@name('Customer')
export class Customer {
  public custId: string;
  public name: string;
}

Then consume with the following helper:

export const nameKey = Symbol('name');

/**
 * To perserve class name though mangling.
 * @example
 * @name('Customer')
 * class Customer {}
 * @param className
 */
export function name(className: string): ClassDecorator {
  return (Reflect as any).metadata(nameKey, className);
}

/**
 * @example
 * const type = Customer;
 * getName(type); // 'Customer'
 * @param type
 */
export function getName(type: Function): string {
  return (Reflect as any).getMetadata(nameKey, type);
}

/**
 * @example
 * const instance = new Customer();
 * getInstanceName(instance); // 'Customer'
 * @param instance
 */
export function getInstanceName(instance: Object): string {
  return (Reflect as any).getMetadata(nameKey, instance.constructor);
}

Extra info:

  • You may need to install reflect-metadata
  • reflect-metadata is pollyfill written by members ot TypeScript for the proposed ES7 Reflection API
  • The proposal for decorators in JS can be tracked here
Etam answered 19/6, 2020 at 13:43 Comment(3)
Hello, thanks for this solution! But trying to use you decorator, I get "Reflect.metadata is not a function" error. To solve this "reflect-metadata" package has to be installed (npmjs.com/package/reflect-metadata) Could you please integrate this info in you answer?Nuthouse
@Nuthouse you must be import the reflect-metadata package first. Code import "reflect-metadata"; into top of your source file. then, use reflect.Generic
@WooyoungTylerKim that's what i did ;) i was just asking to highlight this in the answer to make it even more useful.Nuthouse
A
19

See this question.

Since TypeScript is compiled to JavaScript, at runtime you are running JavaScript, so the same rules will apply.

Adames answered 28/11, 2012 at 21:47 Comment(0)
B
7

In Angular2, this can help to get components name:

    getName() {
        let comp:any = this.constructor;
        return comp.name;
    }

comp:any is needed because TypeScript compiler will issue errors since Function initially does not have property name.

Brockbrocken answered 17/6, 2016 at 22:7 Comment(3)
however this will not work if you minify/uglify your codeBrockbrocken
to get a usable 'name' of a component you're better off getting the tagName of element.nativeElement - On a directive you can get the component's name like this @Optional() element: ElementRef<HTMLElement> and then use if (element != null && element.nativeElement.tagName.startsWith('APP-')) { this.name = element.nativeElement.tagName; } Nee
(and tag names don't get minified)Nee
B
7
  • Had to add ".prototype." to use : myClass.prototype.constructor.name .
  • Otherwise with the following code : myClass.constructor.name, I had the TypeScript error :

error TS2339: Property 'name' does not exist on type 'Function'.

Byrdie answered 29/5, 2017 at 10:1 Comment(0)
A
3

The full TypeScript code

public getClassName() {
    var funcNameRegex = /function (.{1,})\(/;
    var results  = (funcNameRegex).exec(this["constructor"].toString());
    return (results && results.length > 1) ? results[1] : "";
}
Abisia answered 11/6, 2013 at 13:24 Comment(1)
You might get some problems if you minimize and optimize your typescript/javascript code. It might change function names and then your class name comparison might be wrong.Proteus
R
0

I don't know since when this is possible, but right now it is:

class BeeKeeper {
  hasMask: boolean;
}

class ZooKeeper {
  nametag: string;
}

class Animal {
  numLegs: number;
}

class Bee extends Animal {
  keeper: BeeKeeper;
}

class Lion extends Animal {
  keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
  console.log(c.name) //this will give the class name
  return new c();
}

createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;

Credits to this tutorial

Rapscallion answered 10/6, 2023 at 10:14 Comment(0)
A
-1

If you already know what types to expect (for example, when a method returns a union type), then you can use type guards.

For example, for primitive types you can use a typeof guard:

if (typeof thing === "number") {
  // Do stuff
}

For complex types you can use an instanceof guard:

if (thing instanceof Array) {
  // Do stuff
}
Absorptance answered 14/8, 2017 at 18:49 Comment(2)
I guess its because your answer is not related to the question. The question was to get the class name not to conditionally Do stuff on instance type.Berrios
found it usefulDidactic

© 2022 - 2024 — McMap. All rights reserved.