Typescript's Class-method concept

I am interested in implementing a class-based method in Typescript where a method defined on a base class can access the subclass from which it was called. While this behavior is possible in Python, I have not found an equivalent in Typescript. What would be the best approach to achieve this in Typescript?

Python Example:

In Python, the following example demonstrates what I aim to achieve.

# Base class
class Model:
    objects = []

    def __init__(self, objId):
        self.objId = objId

        Model.objects.append(self)

    @classmethod
    def getById(cls, objId):
        # Method accesses subclass using "cls" parameter
        objects = [obj for obj in cls.objects if obj.objId == objId]
        if len(objects) > 0:
            return objects[0]
        else:
            print("error")

# Subclass
class Foo(Model):
    def __init__(self, objId, name):
        self.objId = objId
        self.name = name

        Foo.objects.append(self)

Foo(1, "foo")
Foo(3, "bar")

# Call method on subclass
foo = Foo.getById(1)
bar = Foo.getById(3)

print(foo.name)  # outputs "foo"
print(bar.name)  # outputs "bar"

Foo.getById(2)  # outputs "error"

Typescript (not functioning as intended):

The following example illustrates a rough equivalent in Typescript, but it does not work due to the absence of class methods.

class Model {
    static objects: Model[]

    id: number

    constructor (id) {
        this.id = id

        Model.objects.push(this);
    }

    // Here "cls" should refer to the class on which this method is called
    static getById (id): cls {
        let item = cls.objects.find(obj => obj.id == id);
        if (item === undefined) {
            console.log("error");
        } else {
            return item;
        }
    }
}

class Foo extends Model {
    name: string

    constructor (id, name) {
        super(id);

        this.name = name

        Foo.objects.push(this);
    }
}


new Foo(1, "foo");
new Foo(3, "bar");

// Here cls === Foo
let foo = Foo.getById(1);
let bar = Foo.getById(3);

console.log(foo.name);
console.log(bar.name);

Foo.getById(2)

While this functionality is simple with a single class, I am struggling to find a way to use a method like this for multiple classes without re-declaring it for each one.

Additional Question:

Is there a way to have an "objects" static property on each subclass, typed specifically to that subclass, without manual redeclaration?

class Model {
    static objects: Model[]

class Foo extends Model {
    static objects: Foo[]

class Bar extends Model {
    static objects: Bar[]

I desire this functionality without having to declare the "objects" property separately for each subclass. Is there a solution for achieving this?

Answer №1

When it comes to a static method, the context of "this" refers to the class where it was called. To customize the type of "this" for the method getById, you can specify that getById is applicable to any subclass of Model (meaning any object with a construct signature that constructs a subtype of Model) and returns the instance type of that specific class. This code snippet assumes that all objects from subclasses are stored in one shared array called Model.objects:

class Model {
    static objects: Model[]

    id: number

    constructor (id) {
        this.id = id

        Model.objects.push(this);
    }

    static getById<M extends Model>(
        this: { new(...args: any[]): M }, id): M {
        let item = Model.objects.find(obj => obj.id == id);
        if (item === undefined) {
            console.log("error");
        } else {
            return <M>item;
        }
    }
}

It's worth noting that this implementation has a flaw because someone could potentially invoke Foo.getById with an ID corresponding to a Bar instead of a Foo.

If you prefer to have a separate objects array for each subclass, you will need to manually initialize the array in every subclass, modify the Model constructor to add elements to the objects array of the current class (referenced as this.constructor after declaration), and then update getById to utilize this.objects after confirming its existence.

class Model {
    static objects: Model[]
    "constructor": {
        // This implementation is slightly unsound: the element type
        // is actually the instance type of the constructor.  At least
        // the interface provided is safe.
        objects: Model[]
    };

    id: number

    constructor (id) {
        this.id = id

        this.constructor.objects.push(this);
    }

    static getById<M extends Model>(
        this: { new(...args: any[]): M, objects: M[] }, id): M {
        let item = this.objects.find(obj => obj.id == id);
        if (item === undefined) {
            console.log("error");
        } else {
            return item;
        }
    }
}

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

What can be used in place of Subject.prototype.hasObservers?

I have recently come across the version 4 of RxJS, and noticed that the method hasObservers on Subjects seems to have been removed. I am now faced with the challenge of migrating, as this removal is not documented on the migration guide. hasObservers: fun ...

What is the best way to retry an action stream observable in Angular/RxJS after it fails?

Kindly disregard the variable names and formatting alterations I've made. I've been attempting to incorporate RxJS error handling for an observable that triggers an action (user click) and then sends the request object from our form to execute a ...

Stylishly incorporating components in higher-order components

Trying to enhance my component wrapper with styles using a higher order component has led to Typescript flagging an error with ComponentWithAdddedColors. type Props = { bg?: string; }; function withColors<TProps>( Component: React.ComponentType ...

React and Enzyme are coming up empty-handed when trying to locate any elements within a functional component

After creating a simple functional component in React with Typescript, I encountered an issue while testing it. Every time I try to gather the divs, I keep receiving an empty object {}. Here is how the component is structured: export const TestComponent ...

Encountered an issue while resolving symbol values statically within my exclusive set of modules

Encountered an error while resolving symbol values statically. The function 'DataModule' is not supported. Consider using a reference to an exported function instead of a function or lambda, resolving the symbol DataModuleRoot in E:/shopify-clien ...

Proper Input Component Typing in React/Typescript with MobX

Looking to create a custom React Input component using Typescript for MobX that requires the following input props: a mobx store stateObject a key from that store stateKey I want Typescript to ensure that stateObject[stateKey] is specifically of type str ...

The request/response is missing property "x" in type "y" but it is required in type "z" during fetch operation

I have configured an interface specifically for utilization with an asynchronous function: interface PostScriptTagResponse { script_tag : { readonly id : number , src : string , event : string , readonly created_at : string , readonl ...

Utilizing CSS files to incorporate loading icons in a component by dynamically updating based on passed props

Is it possible to store icons in CSS files and dynamically load them based on props passed into a component? In the provided example found at this CodeSandbox Link, SVG icons are loaded from the library named '@progress/kendo-svg-icons'. Instea ...

Strategies for displaying error messages in case of zero search results

I am currently developing a note-taking application and facing an issue with displaying error messages. The error message is being shown even when there are no search results, which is not the intended behavior. Can someone help me identify what I am doing ...

Is it possible to inject a descendant component's ancestor of the same type using

When working with Angular's dependency injection, it is possible to inject any ancestor component. For example: @Component({ ... }) export class MyComponent { constructor(_parent: AppComponent) {} } However, in my particular scenario, I am tryin ...

Error: An unexpected identifier was found within the public players code, causing a SyntaxError

As a newcomer to jasmine and test cases, I am endeavoring to create test cases for my JavaScript code in fiddle. However, I'm encountering an error: Uncaught SyntaxError: Unexpected identifier Could you guide me on how to rectify this issue? Below is ...

Tips for resolving Circular dependency issue in node.js?

While working on a post request, I encountered an issue with the code below: try{ const _id = await db.collection('UserInformation').insertOne(userObj); await db.collection('LoggedInUser').updateOne({ userId: _id }, { '$set&ap ...

Mapbox GL JS stops displaying layers once a specific zoom level or distance threshold is reached

My map is using mapbox-gl and consists of only two layers: a marker and a circle that is centered on a specific point. The distance is dynamic, based on a predefined distance in meters. The issue I'm facing is that as I zoom in and move away from the ...

The type x cannot be assigned to the parameter '{ x: any; }'

Currently learning Angular and Typescript but encountering an error. It seems to be related to specifying the type, but I'm unsure of the exact issue. Still new at this, so any guidance is appreciated! src/app/shopping-list-new/shopping-edit/shopp ...

Incorporating an additional ion-item alongside the existing one instead of substituting it

I am retrieving a list of questions from an API with pagination. I have a button that triggers a function to load the next page of questions. Instead of replacing the previous page, I want to append the new questions below the existing ones. Here is my cur ...

Issue with validating the date picker when updating user information

Trying to manage user accounts through a dialog form for adding and updating operations, where the type of operation is determined by a variable injected from the main component. Encountered an issue while launching the update process - the date picker tri ...

What are the distinctions in type-narrowing when assigning values using ternary expressions versus if-else statements?

It seems that the type checker is handling the typing of m in print() differently based on whether m was assigned through a ternary expression or an if-else statement. What sets apart the first line in the print() function from the commented code below it? ...

Utilizing Typescript for parsing large JSON files

I have encountered an issue while trying to parse/process a large 25 MB JSON file using Typescript. It seems that the code I have written is taking too long (and sometimes even timing out). I am not sure why this is happening or if there is a more efficien ...

The Jest type definitions seem to be malfunctioning in this TypeScript project

Recently, I began a new Typescript project utilizing GTS. While the typings are functioning correctly for regular *.ts files, I am encountering difficulties in getting *.spec.ts files to work. Issue Each jest function is being flagged as red by ESLint wit ...

Set an array to a JSON object as a fresh entity

My challenge involves working with an array that contains certain values. let myArray = [value1, value2]; I am looking to generate a JSON object in the format shown below: { "field": "[value1, value2]" } ...