Typescript: utilizing self-referencing static properties in classes

There is a blog post discussing the Github issue related to Polymorphic this within static methods. Additionally, there is a question thread on Stack Overflow that addresses this topic here. These resources explore potential solutions and workarounds for handling the issue specifically in static class methods. However, there seems to be a lack of reference on resolving the problem with static class members. We are faced with a scenario where our model class includes several static members containing maps with model fields as keys. In inherited classes, we utilize property decorators to tag these model fields (with the inherited classes solely defining field definitions). The decorator plays a role in adding the fields to the static maps defined in the base class. Refer to the code snippet below or try it out in the playground

function field(): any {
    return function (target: Base, propertyKey: ModelFields<Base>, _descriptor: PropertyDescriptor) {
        if (!target.constructor.fields) {
            target.constructor.fields = {};
        }
        target.constructor.fields[String(propertyKey)] = String(`s_${propertyKey}`);
    };
}

class Base {
    ['constructor']: typeof Base;

    static fields: Record<string, string>;
    // The above is intended to be Record<keyof ModelFields<this>, string> but 'this' is not permitted here
}

class A extends Base {
    @field()
    public propA: string = 'A';
}

class B extends Base {
    @field()
    public propB: string = 'B';
}

type ModelFields<T extends Base> = Required<Omit<T, keyof Base>>

console.log(B.fields) // Type of B.fields appears to be incorrect at this point

The current definition of static fields relies on Record<string, string>, which doesn't specify what keys exist in it, even though we know the valid keys should be keyof ModelFields<this>. Unfortunately, using this isn't allowed in this context.

Are there any solutions available to ensure the typing of fields is accurate?

Answer №1

If you're aiming for a specific outcome, there are steps you can take to move closer towards achieving it.

One obstacle, as you've identified, involves the absence of polymorphic behavior in static members. One workaround is replacing the field with a method that utilizes generic types to capture the call site type:

class Base {
    ['constructor']: typeof Base;

    static fields: Record<any, any>;
    static getFields<TThis extends typeof Base>(this: TThis)  {
        type I = InstanceType<TThis>;
        return this.fields as { 
            [P in keyof I] : string
        }
    }
}

Playground Link

Another challenge is the inability to filter members based on decorators (which aren't represented in the type system). To address this, consider adding a brand to all fields marked with a decorator and then filtering based on that brand:


class Base {
    ['constructor']: typeof Base;

    static fields: Record<any, any>;
    static getFields<TThis extends typeof Base>(this: TThis)  {
        type I = InstanceType<TThis>;
        return this.fields as { 
            [P in keyof I as I[P] extends FieldBrand<unknown> ? P : never] : string
        }
    }
}

declare const fieldBrand: unique symbol;
type FieldBrand<T> =T &  { [fieldBrand]?: never }
type UnBrandField<T> = T extends FieldBrand<infer U> ? U: never;
class A extends Base {
    @field()
    public propA: FieldBrand<string> = 'A';
}

Playground Link

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

Encountering issues with Next.js routing - Pages failing to load as expected

Having some trouble with the routing in my Next.js application. I've created a page named about.tsx within the "pages" directory, but when trying to access it via its URL (localhost:3000/about), the page fails to load correctly and displays: This Pa ...

typescript - add a global library import statement to every script

Recently, I began working on a TypeScript project in Node.js. I am looking to add import 'source-map-support/register'; to all of my .ts files for better visibility of TS source in stack traces. Is there a method to implement this without manuall ...

Using a map feature to extract properties and then transforming them into strings

Here is the data I have: let myInputArray = [ { "id": 1, "commercialRanges": [ { "rangeId": "305", "rangeName": "FIXE" }, { "rangeId": "306", "rangeName": "P ...

What is the most effective way to retrieve the coordinates of a specific element in a React TSX component?

Using React alongside Typescript, I am trying to determine how to retrieve the coordinates of a sub-component within a React class that I have created. I came across this article: https://medium.com/@chung.andrew7/finding-the-absolute-positions-of-react-c ...

Troubleshooting: Icon missing from React vscode-webview-ui-toolkit button

In the process of developing a VSCode extension using React and the WebUi Toolkit library for components, I encountered an issue with adding a "save" icon to my button. I diligently followed the documentation provided by Microsoft for integrating buttons i ...

Troubleshooting Typescript References

Currently, I have a web application that conducts basic analytics on user-imported data. Users can map columns and view property information on a map, as well as various statistics related to the properties. We are in the process of implementing a new fea ...

I'm curious if it's possible to set up both Tailwind CSS and TypeScript in Next.js during the initialization process

When using the command npx create-next-app -e with-tailwindcss my-project, it appears that only Tailwind is configured. npx create-next-app -ts If you use the above command, only TypeScript will be configured. However, running npx create-next-app -e with ...

What is the best way to modify a data parameter in Angular 2?

I am facing an issue while trying to change a data parameter in my component file: this.commandList.ListesCommandesATransmettre.forEach(data => { this.catalogs.forEach(catalog => { if (catalog.Libelle === data.Catalogue) { if ...

Displaying messages in an Angular 2 chatbox from the bottom to the top

As a newcomer to using typescript, I am currently working on an angular 2 project and facing some challenges with creating a chatbox. My goal is to have new messages displayed at the bottom while pushing the old ones up one line at a time, as shown in this ...

Utilizing Angular 2's ngModel feature for dynamic objects and properties

Within my TypeScript file, I am dynamically generating properties on the object named selectedValsObj in the following manner: private selectValsObj: any = {}; setSelectedValsObj(sectionsArr) { sectionsArr.forEach(section => { section.questions. ...

Common problem encountered with TypeScript is the preference for using import instead of require statements

Is there a correct way to handle the issue of using import over require breaking type checking? Can imports be cast, and are all require's replaceable with import's? https://i.sstatic.net/iihi3.png Left: Property 'get' does not exist. ...

Encountered a PrismaClientValidationError in NextJS 13 when making a request

I am currently working on a school project using NextJS 13 and attempting to establish a connection to a MYSQL database using Prisma with PlanetScale. However, when trying to register a user, I encounter the following error: Request error PrismaClientValid ...

Problem encountered while saving data to firestore

Encountering difficulties when attempting to save and retrieve data from firestore. The code snippet provided below is failing, despite its simplicity and my stable internet connection. Even upgrading to websdk 5.0.4 has not fixed the issue. save = async ...

Navigating through Expo with Router v3 tabs, incorporating stack navigation, search functionality, and showcasing prominent titles

I've been working on designing a navigation header similar to the Apple Contacts app, with a large title and search function, but only for the Home Screen. All other tabs should have their own unique settings, like different titles or hidden navigatio ...

There were no visible outputs displayed within the table's tbody section

import React from 'react'; export default class HelloWorld extends React.Component { public render(): JSX.Element { let elements = [{"id":1,"isActive":true,"object":"Communication","previ ...

There was an issue found in the array.d.ts file at line 483, character 22

Trying to understand the reason behind a successful local project build but a failure on the build server Both machines are using the same package.json "name": "UDP", "version": "0.0.1", "license": & ...

The 'authorization' property is not available on the 'Request' object

Here is a code snippet to consider: setContext(async (req, { headers }) => { const token = await getToken(config.resources.gatewayApi.scopes) const completeHeader = { headers: { ...headers, authorization: token ...

Tips for differentiating between elements with identical values in an HTML datalist using Angular

My boss is insisting that I use a datalist in our website interface to select an employee, even though there's no way to determine if the user typed in the name or picked from the list. The challenge is that the list must only display full names, but ...

`The challenges of optimizing performance in a PrimgNg p-listBox`

I am currently experiencing performance issues with my application using PrimeNG Listbox to load 20,000 values from an API. The large amount of data is causing crashes and delays. I need assistance in optimizing the performance of the application. <p ...

Guide on properly specifying mapDispatchToProps in a component's props interface

In my project, I have a connected component utilizing mapStateToProps and mapDispatchToProps along with the connect HOC from react-redux. My goal is to create concise and future-proof type definitions for this component. When it comes to defining types fo ...