19

I have an abstract class Router:

export abstract class Router {
}

And an interface like this

interface IModule {
    name: string;
    forms: Array<IForm>,
    route: typeof Router
}

Now I have a class which looks like this, and many others based on Router abstract

export class Module1 extends Router {
}

Now, I want to instantiate the route like this:

let module: IModule = { 
    name: "My Module",
    forms: [],
    route: Module1
}

let router = new module.route(); 
// TS2511: Cannot create an instance of an abstract class.

The code run just fine, and the instance of router = new Module1() is made properly, but I obviously doesn't write it properly in Typescript because I see TS2511: Cannot create an instance of an abstract class. when transpiling.

What is the correct way to define this?

2
  • are you sure Module1 implements all the abstract method defined in Router?? Commented Sep 16, 2018 at 20:38
  • I realize this is an older question, but I ran into the same issue recently, and found a way that is clean and type-safe using more recent TypeScript features (at the time the question was asked and answered, it was just merged so it wasn't well-known). For people visiting, please see my answer below. Commented Jan 31 at 9:29

4 Answers 4

27

When you have an abstract class, it's constructor is only invokable from a derived class, since the class is abstract and should not be instantiated. This carries over when you type something as typeof AbstractClass, the constructor will not be callable.

The solution would to be to not use typeof Router but rather a constructor signature that returns a Router

interface IModule {
    name: string;
    forms: Array<IForm>,
    route: new () => Router
}

Another solution if you need to access static methods of Router is to create an interface derived from typeof Router which will erase the abstractness of the constructor:

export abstract class Router {
    static m(): void { console.log("Router"); }
}
type RouterClass = typeof Router;
interface RouterDerived extends RouterClass { }

interface IModule {
    name: string;
    route: RouterDerived
}

export class Module1 extends Router {
    static m(): void { console.log("Module1"); }
}

let module: IModule = { 
    name: "My Module",
    route: Module1
}

module.route.m();
let router = new module.route(); 
Sign up to request clarification or add additional context in comments.

4 Comments

Wait. I copied and pasted the code above (playground) and still get the same "Cannot create an instance of an abstract class." error.
Same issue as @semisecure
This solution is outdated, It doesn't work with TypeScript 4.4.3, but it works with TypeScript 3.9.9.
and what solution for 4.4> ?
10

Note, second part of the accepted answer covers:

if you need to access static methods of Router ...

The proposed solution doesn't actually work. Just spent fair bit of time trying to figure this out so though I'd add: the way to do this is:

type RouterDerived = {new (): Router} & typeof Router;

Full example playground.

2 Comments

I guess it should be new () => Router (instead of :), shouldn't it?
Unfortunately it doesn't work when new must accept parameters, new(...args: any[]).
1

Your intention is to say that .route property of IModule is any class (a.k.a constructor, a.k.a new-able function) that returns an instance derived from Router, while the following definition:

interface IModule {
    route: typeof Router
}

… basically says that .route is the Router class itself (this is not exactly true but it is fine for our intents and purposes). Since Router class is abstract, and abstract classes cannot be instantiated, you get an error:

new module.route()
// Error: Cannot create an instance of an abstract class

You need to fix the definition from "the Router constructor itself" to "any class that returns an instance derived from Router". You can do it in two ways.

If you do it loosely (similar to setting the type to any or unknown), you get the basic functionality you need, but you can't use methods and properties of the actual constructor you provide there, because you don't give this information to TypeScript:

interface IModule {
    route: new () => Router
}

const module: IModule = {
    route: Module1,
}
 
new module.route()

Try it

Alternatively, you can do it strictly (similar to specifying an exact type of a variable), which would mean creating a type argument, which would mean defining IModule as a generic:

interface IModule<R extends new () => Router> {
    route: R
}

const module: IModule<typeof Module1> = { 
    route: Module1
}

new module.route()

Try it

Comments

0

In short, the type-safe and non-duplicating way to define type for a non-abstract contructor of the same signature as given abstract class is new (...args: ConstructorParameters<typeof AbstractClass>) => AbstractClass;. See TS docs. So for case in this question, it would be:

let module: IModule = { 
    name: "My Module",
    forms: [],
    route: new (...args: ConstructorParameters<typeof Router>) => Router
}

Longer explanation:

What existing answers don't mention is the case when a constructor of the base class has a non-empty signature, i.e.

abstract class Router {
  constructor(name: string) {
  }
}
export class Module1 extends Router {
}

, creating a type with new () => Router won't work, as the new () function needs params in the definition. As suggested in a comment, it's possible to use new (...args: any[]) => Router, but that erases the type-safety of constructor, and allows calling it with any arbitrary arguments, potentially leading to issues. Defining the new () signature manually, like so new (name: string) => Router, leads to code duplication, as you're defining the same signature in two places.

That's why the ConstructorParameters<T> utility type is needed.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.