How can you annotate and inherit a class method that returns an array of itself?

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?

Answer №1

Indicate this[] as the result type of your function:

class PageElement {
  getAll(): this[]  {}
}

class InputElement extends PageElement {
  aSpecificInputElementMethod() {}
}

const inputElement = new InputElement();
const inputElements = inputElement.getAll();

inputElements[0].aSpecificInputElementMethod(); // permissible;

View it in real time on TypeSciprt Playground.

Answer №2

While not the most ideal approach, one option is to define the return type as the type argument

getAll<T>(): T[]

Then you can call it like this

getAll<InputElement>

However, there are some drawbacks:

  • You must specify the type argument each time you use getAll
  • The type argument must always be a valid type
  • You can potentially pass any type of argument

Overall, there isn't a lot of type safety because it's your responsibility to ensure the type argument remains valid with each call to getAll. This lack of control could lead to issues when working collaboratively.

If anyone has ideas on how to restrict the generic type to only instances of a specific type, please share.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Having difficulty installing npm on Mac m1 with Monterey operating system

npm install npm ERR! code ENOENT npm ERR! syscall open npm ERR! path /Users/ahmed/package.json npm ERR! errno -2 npm ERR! enoent ENOENT: no such file or directory, open '/Users/ahmed/package.json' npm ERR! enoent This issue is likely due to npm n ...

Make sure to update the value of a Mongoose document only if it has been

I am looking to update a specific value in the document only if it is newly defined. If the value is not defined, I want to leave it as it is in the document. For example: If this particular document exists in the database: { "_id": "592c53b3bdf350ce00 ...

Arranging array elements by both date and alphabetical order using Javascript

Is there a way to sort the data by both date and alphabet at the same time? Alphabetical order seems fine, but the date sorting isn't working correctly. Thank you for any solutions. Data structure : [{ productId: 21, title: "Huawei P40 L ...

Execute an AJAX call to remove a comment

Having some trouble deleting a MySQL record using JavaScript. Here is the JavaScript function I am trying to use: function deletePost(id){ if(confirm('Are you sure?')){ $('#comment_'+id).hide(); http.open("get","/i ...

Use the document.getElementById function in JavaScript to dynamically retrieve elements based on user input in an HTML document. This will

I am currently working towards creating a gallery using pure JavaScript. To start, I am experimenting with a model that involves two buttons - when button #1 is clicked, the string "1.jpg" appears below, and when button #2 is clicked, "2.jpg" is displayed. ...

Storing and retrieving text in a file using JavaScript: A step-by-step guide

I have a command set up where if a user is mentioned, the discord bot will save the mentioned user's name in a file (I'm utilizing discord.js and node.js). Below is my code snippet: const prv = require('C:/Users/Kikkiu/Desktop/prova.txt&apo ...

Execute script when on a specific webpage AND navigating away from another specific webpage

I created a CSS code that adds a fade-in effect to the title of my website, and it works perfectly. However, I now want to customize the fade-in and fade-out effect based on the page I am transitioning from. My desired outcome: If I am leaving the "Biolo ...

Is there a way to receive notifications on an Android device when the real-time data updates through Firebase Cloud Messaging (FC

I am attempting to implement push notifications in an Android device using Firebase Realtime Database. For example, if an installed app is killed or running in the background, and a user posts a message in a group (resulting in a new child being added in t ...

When attempting to clear a specific word or character, the entire text is cleared instead

Currently, I am utilizing fabric js along with reactjs to incorporate text onto the canvas. While the text is successfully added, I encountered an issue when attempting to clear specific characters or selected words, resulting in the whole text being cle ...

Is it possible for OpenFin to store logs in a secure database and what is the process for accessing logs located at %LocalAppData%openfinapps<app>app.log

System Information Here are the details of the system setup: OpenFin Process Manager Version: RVM = 8.1.0.4 Node.js: v16.15.0 Windows 10 Angular Application with C# .NET backend Issue: The current setup saves all application logs locally on users' ...

What is the best way to apply changes to every class in JavaScript?

Check out this HTML and CSS code sample! body{ font-family: Verdana, Geneva, sans-serif; } .box{ width: 140px; height: 140px; background-color: red; display: none; position:relative; margin-left: auto; margin-right: auto; } .bold{ font ...

What is the most effective way to manage and respond to multiple events while also organizing the overall structure of

I am currently in the process of planning a complex web application using WebGL and Three.js. As I experiment with different tests, I have encountered a problem that is raising many questions for me. I am unsure of the correct approach to take and would gr ...

Angular - Highlight a section of a string variable

Is there a way to apply bold formatting to part of a string assigned to a variable? I attempted the following: boldTxt = 'bold' message = 'this text should be ' + this.boldTxt.toUpperCase().bold() ; However, the HTML output is: thi ...

What are the best ways to store internal files in node.js for faster access?

I have been utilizing routing functions like the one mentioned below to replicate the overall design of my website (A.jade): exports.overview = function(req, res, next) { res.render('A', { main: jade.renderFile('./views/B.jade' ...

Create an array of arrays within a loop using TypeScript

My application contains an object with dates and corresponding time arrays. The console log output is displayed below: 32: { 1514160000: Array [ 1200, 1500 ], 1514764800: Array [ 1200, 1500 ], 1515369600: Array [ 1200, 1500 ], 1515974400: Array [ 700, 12 ...

Difficulty encountered when combining create-react-app with typescript and immutable.js

After realizing that create-react-app now supports typescript, I encountered some issues while trying to transfer my current codebase from react-scripts-ts. Most of my classes are based on Record and cannot be constructed anymore due to errors like: Cannot ...

How can TypeScript be used to enable CSV or PDF export in a material-react-table?

Is it possible to incorporate the ability to export data to CSV or PDF in a material-react-table? While I am familiar with how to do this with a Material UI table, I have not been able to find the same functionality for the material-react-table. Thank you ...

What is the process for arranging multiple text boxes beside a radio button upon selection?

Displayed below is the HTML code for a page featuring three radio buttons- <html> <body> <form> <input type="radio" name="tt1" value="Insert" /> Insert<br /> <input type="radio" name="tt2" value="Update" /> Update<b ...

Facebook and the act of liking go hand in hand, growing together

I am working on a website where I want to include Facebook like and share buttons with counters. To achieve this, I used Facebook's own links to generate these buttons for the specific URL. The issue I encountered is that when I like or share the page ...

Error in Angular6: Why can't handleError read injected services?

It appears that I am facing an issue where I cannot access a service injected inside the handleError function. constructor(private http: HttpClient, public _translate: TranslateService) { } login(user: User): Observable<User> { ...