Typescript Decorator function generic typing

Viewed 389

I am creating an Express rest API and also looking into creating my own decorator functions. I am now creating a decorator for a controller class.

But whenever I want to add this decorator to the controller I get the following error:

Unable to resolve signature of class decorator when called as an expression. Type 'Constructable' is not assignable to type 'typeof ZoneController'. Property 'getZones' is missing in type 'RestController' but required in type 'ZoneController'.

This error does not popup in the stackblitz (it should popup at the decorator on the ZoneController), but for the code see the Stackblitz

This makes sense because the RestController is the parent class of the ZoneConroller, so it does not know of any methods. In order to solve this I want to add a generic typing to the controller decorator:

type Constructable<T> = new (...args: any[]) => T

export function Controller<T extends RestController>(restCtor: Constructable<T>): Constructable<T> {
  return class extends restCtor {
    router = null
  }
}

But this causes the following error:

Class '(Anonymous class)' incorrectly extends base class 'T'. '(Anonymous class)' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'RestController'

I do not know how I should solve this error. For the error and code see the following Stackblitz. I have found some documentation (here and here) on this error, but it is unclear to me how I should change my code to resolve this error.

How should I change my code in order to resolve this issue? I am using typescript v4.0.2 and the following TSConfig:

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "module": "esnext",
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2015",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ]
  }
}
1 Answers

The definition for a class can be written using an interface:

interface SomeClass {
  new(...args: any[]): SomeClass
}

Following the same pattern, I think you'll want to change your definition of Constructable:

type Constructable<T> = new (...args: any[]) => Constructable<T>
Related