In the following example, I present a simplistic representation of code that may not align with standard HTML or front-end conventions. Please excuse any confusion this may cause.
TL, DR
I am facing challenges in specifying a return type for a method that is intended to return an array of class instances from the same class where the method was invoked.
Returning to the illustrative code snippet:
PageElement.ts
class PageElement {
public element: HTMLElement
constructor(
public elementSelector: string,
public elementName: string,
public description: string,
) {
this.element = document.getElementById(this.elementSelector)!;
}
getAll() {
let foundElements = document.querySelectorAll(this.elementSelector);
let returnElements = [];
let constructor = this.constructor as any;
foundElements.forEach(
element => returnElements.push(
new constructor(
element.attributes['id'].value,
this.elementName + (returnElements.length + 1),
this.description
)
)
)
return returnElements;
}
}
Assume this class has numerous child classes, such as:
class InputElement extends PageElement {
constructor(
elementSelector: string,
elementName: string,
description: string) {
super(elementSelector,
elementName,
description
)
}
exclusiveInputMethod() {
// this method only exists on InputElement
}
}
There are two primary issues that require resolution
getAll() {
let constructor = this.constructor as any;
}
The above logic functions adequately in JavaScript like new this.constructor(...)
.
Nevertheless, TypeScript does not permit this and throws an error:
This expression is not constructable. Type 'Function' has no construct signatures.ts(2351)
Hence, I resort to
let constructor = this.constructor as any;
. Yet, this sacrifices IntelliSense functionality and jeopardizes type safety.
Naturally, one might suggest: “Simply return an array of PageElement
, duh”:
getAll() {
let foundElements = document.querySelectorAll(this.elementSelector);
let returnElements = [];
foundElements.forEach(
element => returnElements.push(
new PageElement(
element.attributes['id'].value,
this.elementName + (returnElements.length + 1),
this.description
)
)
)
return returnElements;
}
However, herein lies the quandary with inheritance:
class InputElement extends PageElement {
constructor(
elementSelector: string,
elementName: string,
description: string) {
super(elementSelector,
elementName,
description
)
}
exclusiveInputMethod() {
// perform actions
}
}
If getAll
were to return an array of PageElement
, I would lose access to exclusiveInputMethod
. When calling InputElement.findAll()
, it would yield PageElement[]
instead of InputElement[]
.
To sum up my queries:
What serves as the suitable counterpart of new this.constructor
in TypeScript to indicate that I'm instantiating an instance of this specific class?
The second question mirrors the first:
How can I annotate (or prompt TypeScript to correctly infer the return type) for the method getAll()
so it consistently recognizes that the return type is an array of class instances from which this method originates?