Instantiate class from a collection of subclasses, while maintaining access to static members in Typescript

61 views Asked by At

I think I may have stumbled into a situation where what I want is best described by having both static and abstract on a member of an abstract class, but that doesn't seem to be possible in typescript.

The closest I can get is the below:

abstract class A {

    s: string;
    n: number;
    static identifier: string;

    constructor(s: string, n: number) {
        this.s = s;
        this.n = n;
    }

}

class B extends A {

    static identifier: string = "B";

}

class C extends A {

    static identifier: string = "C";

}

function identifyAndInstantiate(identifier: string, classes: (typeof A)[]) {

    var identifiedClass = classes.find((classCandidateConstructor: typeof A) => { return identifier == classCandidateConstructor.identifier });
    if (!identifiedClass) return;
    
    //error here: Cannot create an instance of an abstract class.(2511)
    var instance = new identifiedClass("foo", 12);

}

var classes: (typeof A)[] = [
    B,
    C
]

I've tried a variety of different approaches to the collection type. The closest I could get working was a Map from the static member type to instances of the class, but then you have to individually map the values to their classes.

Another thing I tried was to use a Newable<T> type that describes its constructor, but this removes the ability to refer to the subclass' static properties (as does seemingly anything else that makes the constructor available).

Is there a way to both refer to a subclass' static properties and its constructor when having retrieved it from a collection of classes?

2

There are 2 answers

1
Etienne Laurin On BEST ANSWER

Your code works almost as-is, with the following changes:

  • You can't refer to subclass.identifier unless that property is in the type of subclassesOfA
  • You can't instantiate a typeof A but you can instantiate a { new(): A }
function foo(subclassIdentifier: string, subclassesOfA: {
  identifier: string,
  new (): A
}[]): void {
    let subclass = subclassesOfA.find((subclass) =>
        subclass.identifier == subclassIdentifier);
    if (subclass) {
        let instance = new subclass();
        // ...
    }
}
3
Kyle Xyian Dilbeck On

Explanation

This can be done by defining an abstract property in the base abstract class (A) and implementing it in each concrete subclass (B, C, etc.). This way, each subclass has a unique identifier that can be used to distinguish them when stored in a collection.

Simple Code Example

abstract class A {
  static identifier: string;

  abstract someMethod(): void;
}

class B extends A {
  static identifier = 'B';

  someMethod(): void {
    console.log('Method in B');
  }
}

class C extends A {
  static identifier = 'C';

  someMethod(): void {
    console.log('Method in C');
  }
}

function foo(subclassIdentifier: string, subclassesOfA: (typeof A)[]): void {
  let subclass = subclassesOfA.find(subclass => subclass.identifier === subclassIdentifier);

  if (subclass) {
    let instance = new subclass();
    instance.someMethod();
  } else {
    console.error('Subclass not found');
  }
}

// Example usage
foo('B', [B, C]); // Output: Method in B
foo('C', [B, C]); // Output: Method in C

Breakdown

  • Each concrete subclass (B, C, etc.) defines a static identifier property.
  • The abstract base class (A) declares a static property identifier that needs to be implemented by each subclass.
  • The foo function takes an identifier and a collection of subclass constructors and creates an instance of the matching subclass.

You can now refer to both the static properties and constructors of the subclasses while having a common abstract property for identification.