Typescript: Suggestion: typeof of generic type

Created on 23 Jul 2014  路  33Comments  路  Source: microsoft/TypeScript

This issue was previously reported as bug at codeplex, now trying to submit as suggestion.

Below, the Collection.model field is supposed to accept type of TModel rather than instance of TModel:

class Model { }
class Collection<TModel extends Model> {
    model: typeof TModel; //Error: Could not find symbold 'TModel'.
    models: TModel[];
}

class MyModel extends Model { }
class MyCollection extends Collection<MyModel> {
    // This is what we want
    model: typeof MyModel = MyModel;

    // This is what we don't want
    //model: MyModel = new MyModel;
}

var coll = new MyCollection();
var modelType = coll.model;
var newModel = new modelType();

Use of typeof keyword seems to be appropriate here, but compiler doesn't understand typeof of generic type. If we said model: typeof Model; it would compile but that is not exactly what we want.

Some of the workarounds we found are the followings:

class Model { }
class Collection<TModel extends Model> {
        model: { new(): TModel; };
        models: TModel[];
}

class MyModel extends Model { }
class MyCollection extends Collection<MyModel> { }

and

class Model { }
class Collection<TModelConstructor extends typeof Model, TModel extends Model> {
        model: TModelConstructor;
        models: TModel[];
}

class MyModel extends Model { }
class MyCollection extends Collection<typeof MyModel, MyModel> { }

Feature suggestion is to allow typeof generic type.

By Design

Most helpful comment

I have the same problem, while it is understandable to have to pass the constructor as an argument to reference it later (although ironic since we already have the type parameter), the someFunc(constructor: new() => T) approach is unaware of static properties attached to the constructor arg. To get around the syntax error I had to use someFunc(constructor: new() => T | any). After which the compiler allows constructor.staticFoo.

Note, the someFunc(constructor: typeof ParentClass) can also get you half-way there as you can reference the static properties, however the compiler complains when you try to create an instance of the generic type from the constructor arg.

All 33 comments

typeof takes a value and produces the type that the value has. Generic type parameters are _types_; they are already types and there's no need to have an operator to convert them to types.

Your workarounds are actually the correct design because they model the constraint you have -- a zero-argument constructor function for the given type.

See also https://typescript.codeplex.com/discussions/456796

The link was very informative. Thank you.

The first workaround actually causes problems with statics:

class Model {
  static doStuff() { }
}
class Collection<TModel extends Model> {
  // With this workaround, you also have to hack in doStuff() here which causes a lot of duplication.
  model: { new(): TModel; };
  models: TModel[];

  doStuffToModel() {
    // This is okay because we hacked in "new(): TModel"
    var model = new this.model();

    // This is not okay because we didn't hack in "doStuff(): void"
    this.model.doStuff();
  }
}

The second workaround is a lot better but also problematic:

class Model { }
class Collection<TModelConstructor extends typeof Model, TModel extends Model> {
  model: TModelConstructor;
  models: TModel[];

  static createCoolInstance<TModelConstructor extends typeof Model, TModel extends Model>(ctor: TModelConstructor): TModel {

    // "Type 'Model' is not assignable to type 'TModel'." so you have to "<TModel>new ctor();" which seems pretty unnecessary
    return new ctor();

  }
}

Let me explain what I think about this with some pseudo code:

class MyClass {
  static foo() {};
  bar() {};
}

// That does something like:
type MyClassInstanceType = { bar: () => void };

type MyClassConstructorType = { new(): MyClassInstanceType; foo(): void };

var MyClassValue = function() {};
MyClassValue.prototype.bar = function () {};
MyClassValue.foo = function () {};

// So if typeof was a function, it would be something like:
function typeof(classValue: Value): Type {

  return ClassValueToConstructorTypeMap[classValue];

}

My question is, why does typeof take a value? Here is an example:

var inst: MyClass = new MyClass();
var ctor: typeof MyClass = MyClass;

The first "MyClass" refers to MyClassInstanceType and the second one refers to MyClassValue.
On the second line, couldn't the first "MyClass" refer to MyClassInstanceType again and get MyClassConstructorType from that?
If changing typeof to or overloading it with "InstanceType => ConstructorType" is breaking, I think we could easily have another operator for it. (constructorof? staticof?)

And some examples would be:

autoConstructor<T>(ctor: constructorof T): T {

  return new ctor();

}

var inst;

/* Works */
class MyClass { }

inst = autoConstructor(MyClass);

/* Should these work? */
inst = autoConstructor<number>(Number);
inst = autoConstructor(Number);
inst = autoConstructor<string>(String);
inst = autoConstructor(String);
inst = autoConstructor(Date);

/* Maybe: */

// Ok
autoConstructor<T extends MyClass>(ctor: constructorof T)......

// Error: Types given to 'constructorof' must extend a class type 
autoConstructor<T>(ctor: constructorof T)......

Note that new Number() does not produce a number (it's not primitive).

You can already write this:

interface constructorof<T> {
    new (): T;
}

function autoConstructor<T>(ctor: constructorof<T>): T {
  return new ctor();
}

var inst;

/* Works */
class MyClass { }

let c = autoConstructor(MyClass); // c: MyClass

/* Should these work? */
inst = autoConstructor<Number>(Number);
inst = autoConstructor(Number);
inst = autoConstructor<String>(String);
inst = autoConstructor(String);
inst = autoConstructor(Date);

That doesn't provide the actual type for the constructor:

class MyClass {
  static foo() {};
}

interface constructorof<T> {
    new (): T;
}

function autoConstructor<T extends MyClass>(ctor: constructorof<T>): T {

  // No foo here
  ctor.foo();

  return new ctor();
}

What is the reason for having typeof? Wouldn't constructorof cover all use cases of typeof and more? (And also without the created confusion with JavaScript's own typeof.)

There can be more than one constructor function for any given type.

interface SomeObj { ... }
declare var ctor1: {
  new(): SomeObj;
  foo;
}
declare var ctor2: {
  new(): SomeObj;
  bar;
}

function magic<T extends SomeObj>(ctor: constructorof<T>) {
 // can I use ctor.foo here? ctor.bar?
}

typeof exists so that, given a _value_ of some _type_ that is otherwise anonymous, you refer to that anonymous type without having to rewrite it yourself.

Can there be more than one constructor function for any given class type? If not then a constructorof keyword that only works with class types could solve the issue.

class MyClass {
  static foo() {};
}

/*
  MyClass was declared with the `class` keyword.
  We can be sure that it has one constructor.
  We allow constructorof to be used with it.
*/ 
var ctor: constructorof MyClass = MyClass;

Can there be more than one constructor function for any given class type?

Yes.

class Foo { }
declare var factory: {
  new(): Foo;
}

Yes, but typeof doesn't care about that:

class Foo {
  static bar() {};
}
declare var factory: {
  new(): Foo;
  foo();
}

var ctor: typeof Foo = Foo;

ctor.bar; // Here
ctor.foo; // Not here

The constructorof implementation can do the same.

I'm still not sure what you're proposing that is meaningfully different from typeof or new() => T.

Enum don't have constructors so new() => T does not work

Enum don't have constructors so new() => T does not work

And what problem is this presenting you with? Of course enum's don't have constructors. This was (originally) an issue about how to reference the constructor of a generic, which really turned into how to describe a generic that extends a constructor.

I was making a method that took an enum and one of its values and typeof T said T is not found.

function x<T>(_enum: typeof T, value: T){}

This is a different problem... While the the intellisense lists an enumeration type as a typeof there is no way to narrow a generic to be a value of any enum to be able to try to extract the type it comes from. This is covered in #3440 as "accepting PRs". Once that is addressed, what you are talking about should work.

I have the same problem, while it is understandable to have to pass the constructor as an argument to reference it later (although ironic since we already have the type parameter), the someFunc(constructor: new() => T) approach is unaware of static properties attached to the constructor arg. To get around the syntax error I had to use someFunc(constructor: new() => T | any). After which the compiler allows constructor.staticFoo.

Note, the someFunc(constructor: typeof ParentClass) can also get you half-way there as you can reference the static properties, however the compiler complains when you try to create an instance of the generic type from the constructor arg.

I found that a quite weird syntax may replace "typeof T" syntax.

/** Generic class type. */
type Constructor<T> = { new (...args: any[]): T } | ((...args: any[]) => T) | Function;

Possible usage:

/** Generic class type. */
type Constructor<T> = { new (...args: any[]): T } | ((...args: any[]) => T) | Function;


/** Dummy dependency injector */
export class Injector {

  public resolve<T>(x: Constructor<T>): T {
      // Injector implementation, here a dummy one.
      return undefined as any as T;
  }

}

/** A dummy base abstract class to resolve. */
export abstract class ClassToResolve {

  public n: number;

  protected abstract constructor(a: string, b: string) {
  }
}

let injector = new Injector();

const resolved = injector.resolve(ClassToResolve);

// Compiler says that the variable resolved is of type ClassToResolve, correct!
resolved.n = 1000;

// Compiler will fail here, xxx is not a member of ClassToResolve. Correct!
resolved.xxx = "xxx";

Well, it seems to "abuse" the TypeScript interpreter but it works, and makes sense.

Someone have an opinion over this and especially someone maybe knows if this would not work in the next version of TS?

@SalvatorePreviti that's basically the same as the recommended solution in the FAQ, so I'd say it's well-supported :wink:

I'm sorry to piggy back onto this, but I think my question is very much related to this question, but I couldn't quite figure out if it was so. http://stackoverflow.com/questions/43000982/how-to-create-a-factory-that-returns-anonymous-classes-that-implement-a-generic

I'm trying to do the following to remove boiler plate code of creating concrete classes for a payload type.

export interface TypedAction<T> extends Action {
    type: string;
    payload: T;
}
export function createTypedActionConstructor<T>(type: string): TypedAction<T> {
    return class TypedActionImpl implements TypedAction<T> {
        readonly type = type;
        constructor(public payload: T) {}
    }
}

But I get an error because what I'm returning is the class that implements TypedAction, not the instance. The error says:

TS2322 Type 'typeof TypedActionImpl' is not assignable to type 'TypedAction'. Property 'type' is missing.

Is it impossible to return a dynamic implementation of a generic class from TypeScript?

@juanmendes your return type should be new(payload: T) => TypedAction<T>, not TypedAction<T>. You've declared that you return the instance type but you've provided a constructor type

Thanks Ryan, it mostly works, the problem there is that the return of createTypedActionConstructor is not a true type, that is, I can't cast to it.

I don't understand why the typeof operator should not be applied on type arguments. The language specification states in section 4.18.6:

The 'typeof' operator takes an operand of any type and produces a value of the String primitive type. In
positions where a type is expected, 'typeof' can also be used in a type query (section 3.8.10) to produce
the type of an expression
.

So why is something like the followin not allowed?

getConstructor<T>( instance: T )
{
    return instance.constructor as typeof T;
}

typeof X produces the type of the value X. There's no value named T, so the "typeof the value T" question makes no sense. It's like asking how much blue weighs.

@RyanCavanaugh: But if so, why do the specs state that typeof can be used in a type query?

class MyClass {}
let x: typeof MyClass; // MyClass is a type here

@markusmauch

class MyClass {}
let x: typeof MyClass; // MyClass is a value here

x is a variable whose type is the _type_ of the _value_ named MyClass. A class definition introduces a name in the _type_ space that refers to the type of instances of that class, and a name in the _value_ space that refers to the class value (the thing that gets newed).

@aluanhaddad

Well okay, MyClass is a value then. But how do I get the type of the constructor function of the type MyClass?

@markusmauch by writing typeof MyClass in type position. Just as you had done.

Is there a reason typeof MyClass<SpecificType> is not supported when I have it defined as class MyClass<T> { ... }?

I'd like to get the type of the class with a specific type, but without having to create a class that inherits from it:

const MySpecificClass: typeof MyClass<SpecificType> = MyClass;

@leoasis the type argument belongs to the instance side of the class. typeof operator obtains the type of the static side of the class.

Yeah, I know, that's what I wanted to show in the example. In my particular use case, I'm using React, and I have an InfiniteScrollList component that is parametrized on the type of the item it accepts. We did this to force the same type you provide to be the same type you would expect in any callback prop, instead of it being any and making the user specify the type:

The problem there is that when I define the component with a generic type, I can no longer use it in JSX directly, since I cannot write

return (
  <InfiniteScrollList<Item> {...props} />
);

So I tried doing something like this:

const ItemInfiniteScrollList: typeof InfiniteScrollList<Item> = InfiniteScrollList;

but that didn't work.

Couldn't find another way to make that work without creating an empty class that inherits from the parametrized one. That works, but I would like to avoid creating a class (which will be created at runtime) just to enforce type safety.

The workaround mentioned by @SalvatorePreviti and the FAQ only works on classes that don't have any static methods, as the return type is _only_ a constructor.

I can't seem to come up with a decent workaround that would even allow for an identity function that accepts and returns a constructor.

abstract class Animal<T> {
    static identity<T>(T: typeof Animal<T>) : typeof T; // this doesn't work
}

class Dog extends Animal<Dog> {
}

Animal.identity(Dog) // => Dog

A more concrete (and realistic) use-case would be a static method that creates a new copy of the class/constructor (possibly with a different set of static properties, or extra static methods). This is a pattern often seen in ORMs, notably Sequelize's Model.scope() method.

If anybody has ideas about similar ways to achieve the same result whilst working within the boundaries of TypeScript's existing type system, let's get them documented!

class Animal<T> {
    static identity<T extends new (...args: any[]) => Animal<any>>(C: T): T {
        return  C;
    }
}

class Dog extends Animal<Dog> {}

Animal.identity(Dog); // => typeof Dog

Or even

class Animal<T> {
    static identity<T extends new (...args: any[]) => Animal<any>>(this: T) {
        return this;
    }

    static create<T extends Animal<any>>(this: new (...args: any[]) => T) {
        return new this();
    }
}

class Dog extends Animal<Dog> {}

Dog.identity(); // => typeof Dog

Dog.create(); // => Dog

I'm having the same problem, and basically ended up chain casting to any and typeof Model:

// This class is in a 3rd party library:
abstract class Model<T> {
  static count(): number;
}

interface Options<T extends Model<T>> {
  target: new () => T;
}

// `options` type is `Options<T>`
const model = options.target as any as typeof Model;
return model.count();
Was this page helpful?
0 / 5 - 0 ratings