0

Why doesn't the constructor signature match the interface declaration in the following excerpt and how should I re-express it? The reported error is

'Class 'Item' incorrectly implements interface 'ItemClass'. Type 'Item' provides no match for the signature 'new (Scope?: Scope | undefined): Item'.'

The point of this code is factory support for subclasses identified at run-time by a string name coming from a serialisation of the class. The abstract AsyncCtor defines and initialises a Ready property. I could do this directly in

export interface ItemClass {
  Ready: Promise<any>;
  new(Scope?: Scope): Item;
}

export abstract class AsyncCtor {
  public Ready: Promise<any> = new Promise((resolve, reject) => resolve(undefined));
}

export abstract class Item extends AsyncCtor implements ItemClass {
  static Type: Map<string, ItemClass> = new Map<string, ItemClass>();
  static register(typeName: string, typeClass: ItemClass): void {
    this.Type.set(typeName, typeClass);
  }
  public static new(raw: any): Item {
    let graph = typeof raw === "string" ? JSON.parse(raw) : raw;
    let typeClass = Item.Type.get(graph.Type) as ItemClass;
    let item = new typeClass();
    ...
    return item;
  }
  constructor(public Scope?: Scope) {
    super();
  }
}

If I stop declaring the fact that Item implements ItemClass, everything compiles and the Item.new(raw) method works fine, so obviously it actually does implement ItemClass.

Before anyone suggests it, I've already tried

  constructor(public Scope?: Scope | undefined) {
Peter Wone
  • 17,965
  • 12
  • 82
  • 134
  • 1
    [Construct signatures in interfaces are not implementable in classes](https://stackoverflow.com/questions/13407036/how-does-typescript-interfaces-with-construct-signatures-work) – artem Aug 29 '17 at 23:46
  • Thank you. It looks to me like my question is almost a duplicate, with its primary virtue being that it will help people coming from this direction to find understanding. If you would be so kind as to restate your comment as an answer I would like to accept it. – Peter Wone Aug 30 '17 at 00:04
  • Nitzan's answer is on the right track, when you have proper `constructor` and proper static methods in `Item` so that static part of the `Item` conforms to `ItemClass`, you can use any non-abstract descendant of `Item` as second argument for `register`, without explicitly declaring that it implements `ItemClass` (compatibility in typescript is always structural). See also https://stackoverflow.com/questions/39362690/difference-between-the-static-and-instance-sides-of-classes – artem Aug 30 '17 at 00:16

1 Answers1

0

You are mixing static class methods with instance methods.

It should look something like:

type ItemClassConstructor = {
    new (Scope?: Scope): Item;
    create(raw: any): Item;
}

interface ItemClass {
    Ready: Promise<any>;
}

abstract class Item extends AsyncCtor implements ItemClass {
    static Type: Map<string, ItemClassConstructor> = new Map<string, ItemClassConstructor>();
    static register(typeName: string, typeClass: ItemClassConstructor): void {
        this.Type.set(typeName, typeClass);
    }

    public static create(raw: any): Item {
        let graph = typeof raw === "string" ? JSON.parse(raw) : raw;
        let typeClass = Item.Type.get(graph.Type);
        let item = new typeClass();

        return item;
    }

    constructor(public Scope?: Scope) {
        super();
    }
}

(code in playground)

Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
  • No, I'm not, although it does _look_ like that, and I will take your implied advice to avoid naming a static method "new". In an interface `new` refers to a newable function ie the constructor. This is expounded in the question referenced in artem's comment - which also explains the true problem nicely. – Peter Wone Aug 30 '17 at 00:01
  • But your method is `static`, and you can't put it in an interface and then extend that interface expecting it to work. Static methods should be placed in another interface/type which you cannot extend, as I did with the `ItemClassConstructor` type. – Nitzan Tomer Aug 30 '17 at 00:03
  • As I've already said, new in the interface refers to a constructor. It's a terrible choice of name because it induces exactly the confusion you are experiencing. If I rename the static `new` method to `create` the code has exactly the same semantics from the compiler's perspective. – Peter Wone Aug 30 '17 at 00:07
  • But `new`, just like a static method can not be `implemented`, they are not instance methods. Which is why I placed them in a different type called `ItemClassConstructor`. – Nitzan Tomer Aug 30 '17 at 00:10