Home > Net >  Cannot assign an abstract constructor type to a non-abstract constructor
Cannot assign an abstract constructor type to a non-abstract constructor

Time:10-03

how i can solve this issue with typescript ?

ex: When you have a class registers, and when you want creates a instances of all classes registered ?


export abstract class FooAbstract {
    static registers: typeof FooAbstract[] = []; // a registers
    static create<T extends FooAbstract>(constr:  new ()=>T) {
        return new constr();
    }
}

export  class A extends FooAbstract {
   name:string;
}
FooAbstract.registers.push(A); // in my case i use decoration, but not matter !


// what should be the good way here ?
const test = FooAbstract.registers.map((cls)=> cls.create(cls))

enter image description here

Argument of type 'typeof FooAbstract' is not assignable to parameter of type 'new () => FooAbstract'.
  Cannot assign an abstract constructor type to a non-abstract constructor type.ts(2345)

CodePudding user response:

Each class in your registry must be new-able without any arguments. This means it should not be allowed to contain abstract classes, or classes whose constructors require arguments. You need to change the type of your registry to specify this. I've written a NoArgsConstructor helper type to simplify this.

type NoArgsConstructor<T> = new () => T

export abstract class FooAbstract {
    static registers: NoArgsConstructor<FooAbstract>[] = [];
    static create<T extends FooAbstract>(constr: NoArgsConstructor<T>): T {
        return new constr();
    }
}

export class A extends FooAbstract {
    name:string = 'foo';
}
FooAbstract.registers.push(A);

const test = FooAbstract.registers.map(cls => FooAbstract.create(cls));

Note also that the call to the static create method needs to be on FooAbstract, not on cls.

Playground Link

CodePudding user response:

The issue is in this line (constr: new () => T) . create static method expects a regular constructor, not an abstract constructr whereas you are trying to pass an abstract constructor to this method. See here FooAbstract.registers.map((cls) => cls.create(cls)). cls.create expects new () => T but you are trying to use abstract new () => T.

You can add abstract flag/modifier to constr argument.

export abstract class FooAbstract {
    static registers: typeof FooAbstract[] = [];
    static create<T extends FooAbstract>(constr: abstract new () => T) { // add `abstract` modifier
        return new constr();
    }
}


const test = FooAbstract.registers.map((cls) => cls.create(cls))

But above approach, causes a new problem. You are not allowed to create an instance of abstract class, new constr() // <--- error is here.

UNSAFE APPROACH, MIGHT CAUSE AN ERROR IN RUNTIME. SEE @kaya3 COMMENTS

If you want to create an instance of some class, this class should not be an abstract.

Hence, we need to create an empty anonymous class which will extends our abstract class:

export abstract class FooAbstract {
    static registers: typeof FooAbstract[] = [];
    static create = <T extends FooAbstract>(constr: new () => T) => new constr()
}


const test = FooAbstract.registers.map((cls) =>
    cls.create(class extends cls { }) // <--- anonymous class is just a wrapper
)

Playground

There is another one solution. You can just remove abstract keyword from FooAbstract class definition.

P.S. I'm not an OOP expert, so I can't say whether the answer I have provided meets OOP good practices or not. I have shown you just how to fix TS error

P.P.S if using abstract class or even OOP approach is not important for you, I can recommend another approach if you will provide more details to your question.

CodePudding user response:

Related to the awnser of @captain-yossarian i retain this approached where is work fine. But without wrap class where am not fan.

So i return juste a new ()=>T without abstract flag ( Type Guard!)

export abstract class FooAbstract{
    static registers: typeof FooAbstract[] = [];
   // adding abstract, and than  return wihout abstract 
    static create<T extends FooAbstract>(constr: abstract new ()=>T) {
        return new (constr as new ()=>T)();
    }
}

export  class A extends FooAbstract {
   name:string;
}

FooAbstract.registers.push(A); // in my case i use decoration, but not matter !

// laters
const test = FooAbstract.registers.map((cls)=> cls.create(cls ))
  • Related