99

I'm trying to pass a class as a parameter to some function, that will instantiate this class and return it. Here is my code:

module A.Views {
  export class View { ... }
}

module A.App {
  export class MyApp {
    ...
    registerView(viewKlass:A.Views.View):void
    {
        var test = new viewKlass;
    } 
  }
}

When i'm trying to compile this, i'm getting:

(...): Value of type 'Views.View' is not newable.

What am I doing wrong?

If a newable type value is an object constructor how do i pass the constructor function at runtime?

I Hate Lazy
  • 47,415
  • 13
  • 86
  • 77
zw0rk
  • 1,406
  • 1
  • 12
  • 15

8 Answers8

133

We need something to say typeof(MyClass) to distinguish objects from classes in function signatures.

Anyway, you can actually solve you problem by using constructor type signature. Considering this class:

class MyClass {
    constructor (private name: string) {
    }
}

To pass that class as a type that you can then instantiate in a function, you actually have to duplicate the class constructor signature like this:

function sample(MyClass: new (name: string) => MyClass) {
    var obj = new MyClass("hello");
}

EDIT : There is a simple solution found on codeplex:

You have to create an interface for your class like this:

interface IMyClass {
    new (name: string): MyClass;
}

Then, use it in your function signature:

function sample(MyClass: IMyClass) {
    var obj = new MyClass("hello");
}
Yves M.
  • 29,855
  • 23
  • 108
  • 144
Thomas Laurent
  • 1,430
  • 1
  • 9
  • 7
  • 102
    function sample(MyClass: typeof MyClass) should do it. – Corey Alix Jan 09 '14 at 22:33
  • 3
    If you don't know the constructor arguments, you could also use `new (...args: any[]): MyClass` inside your interface. – Toni May 11 '17 at 16:11
  • 2
    Actually, `function sample(MyClass: typeof MyClass)` from Corey comment could **only** work for non-abstract classes. To do the same thing with [abstract classes](https://www.typescriptlang.org/docs/handbook/classes.html#abstract-classes), `function sample(MyClass: new (name: string) => MyClass)` is a better choice. – Hang Yan Nov 13 '21 at 09:22
42

To supplement the other answers; I have a utility type I keep around to ensure a generic parameter/field is a class/newable:

/* new T() */
export type Newable<T> = { new (...args: any[]): T; };

You can then ensure you get a class constructor:

class MyApp<TViewBase>
{
  register<TView extends TViewBase>(view: Newable<TView>) { 
  }
}

the Newable<T> approach works where typeof T --where T is a generic type-- does not.

For example: register<T extends TViewBase>(view: typeof T) results in the following error:

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

Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122
  • 2
    This is a great answer, could you please explain a little bit more how it works? – Bruno Bieri Nov 27 '18 at 09:06
  • 2
    This should be the accepted answer, and honestly, included in TypeScript. This is definitely superior to trying to identify the exact signature of the class' constructor. – Tim Klein Jan 16 '19 at 22:06
  • Awesome answer This helped me with passing a class constructor of a generic child of an abstract class too. – Ryan Bobrowski May 04 '21 at 05:42
  • I think if you replace `any[]` with `never[]` it will make it harder to erroneously call the constructor when you don't really know what arguments it requires. TS will still let you call it with _no_ args, unfortunately, but if you try to pass any args to it you'll get a type-checking error when using `never[]` (when using `any[]`, TS would let you pass anything you want when invoking the constructor). – peterflynn Dec 13 '21 at 07:04
32

Try this:

export class MyApp {
    registerView(viewKlass: typeof A.Views.View): void {
        var test = new viewKlass();
    } 
}
Matthew James Davis
  • 12,134
  • 7
  • 61
  • 90
25

There are 2 ways to pass classes in TypeScript. If you:

  • know the superclass of the class you're passing through (already recommended by Corey Alix):

    class MyClass extends MySuperClass{ }
    
    makeMyClass(classRef: typeof MySuperclass) {
        return new classRef();
    }
    
    makeMyClass(MyClass);
    
  • know the signature of the class's constructor function:

    class MyClass {
        constructor(arg: string) {}
    }
    
    makeMyClass(classRef: { new(arg: string) }) {
        return new classRef('hello');
    }
    
    makeMyClass(MyClass);
    
Bruno Bieri
  • 9,724
  • 11
  • 63
  • 92
Stephen Paul
  • 37,253
  • 15
  • 92
  • 74
15

in case you use Angular, they have implemented Type, its similar to the Meirion Hughes answer:

import {Type} from '@angular/core';

export class MyApp {
  ...
  registerView(viewKlass: Type<A.Views.View>):void
  {
      var test = new viewKlass();
  } 
}
ya_dimon
  • 3,483
  • 3
  • 31
  • 42
3

I know this is an old question, but here goes:

...
registerView(viewKlass: { new(): A.Views.View }):void
{
    var test = new viewKlass();
} 

Answer was found here.

Perspectivus
  • 972
  • 1
  • 9
  • 22
3

Depending on your situation, the three solution bellow might not be enough:

  1. myClass: new (name: string) => MyClass
  2. interface IMyClass { new (name: string): MyClass; }; myClass: IMyClass
  3. myClass: typeof MyClass

There are cases where you would like to call some static methods like myClass.someMethod(). 1 and 2 won't allow you to do that (and pls never use any in your app) because they don't know about that method.

Or there are cases when you want to have MyClass as an abstract class and create an instance of myClass with let instance = new myClass. In that case, 3 will fail.

What I do in these cases is to create a special type for MyClass that will solve the issues I highlighted above, which looks something like this:

type MyClassContructor<T extends MyClass> = typeof MyClass & Constructor<T>;

BTW. Don't forget to add return types to your methods so you get proper intellisense.

Ionel Lupu
  • 2,695
  • 6
  • 29
  • 53
-1

Thank you for the gist - a slight change makes it all work fine. In the example below, we "new up" the TestView to pass in the concrete test view, rather than trying to new it within the method.

module V.Views {
   export class View {
      public someVar: any;
      // the presence of constructor doesn't affect the error triggering
   }
}

module V.App {
    export class Application {
        public registerView(url: string, viewKlass: V.Views.View): void
        {
            var test = viewKlass;
        }
    }
}

var app = new V.App.Application;

class TestView extends V.Views.View {
}

class TestView2 extends V.Views.View {
}

app.registerView('', new TestView())
app.registerView('content/view/:slug/', new TestView2())
Fenton
  • 241,084
  • 71
  • 387
  • 401
  • Can you post your full example code - I've tested my code in the Playground and don't get the error - so your code must contain something different. – Fenton Oct 09 '12 at 15:13
  • 3
    But this doesn't solves my problem. I want to pass a class not an object. :) – zw0rk Oct 09 '12 at 15:32
  • If you want to instantiate the object inside the `registerView` method, you'll have to pass a string, not a `viewKlass`. i.e. `viewKlass: V.Views.View` would have to be `viewKlass: string` and then you could call `var test = new viewKlass();` – Fenton Oct 09 '12 at 15:40