Exploring the hierarchy of inheritance using TypeScript classes

In our development environment, we have a unique "framework" that creates ES5 "classes" in a specific way:

var MyClass = ourClass({
  extends: Base,
  constructor: function MyClass(...){...}
  static: {},
  ...
})

// later somewhere
var myClassInstance = new MyClass(...);

This process involves enhancing the returned constructor with static properties and the inheritance chain of the constructor functions.

For example, in a hierarchy like Base -> Specialized -> EvenMoreSpecialized,

EvenMoreSpecialized.staticChain === [EvenMoreSpecialized, Specialized, Base, Object]
. If we have an instance theInstance of EvenMoreSpecialized, calling theInstance.static('aField') retrieves the first 'aField' property defined in the inherited constructor functions.

I am exploring options to migrate to TS/ES6 classes without compromising the existing functionality of the static() feature. The challenge lies in integrating this feature smoothly into TypeScript while preserving its current capabilities.

The proposed signature for the static() function would be:

static(identifier:string):any

Is there a way to catch misspelled identifiers at compile time? Is it possible to narrow down the return type from any for better type safety?

Here's a more detailed example:

var Base = ourClass({
  extends: Object,
  constructor: function Base(){},
  static: {
    secret: null
  }
})

var Specialized = ourClass({
  extends: Base, 
  constructor: function Specialized() {};
  static: {
    secret: '12345'
});
var EvenMoreSpecialized = ourClass({
  extends: EvenMoreSpecialized,
  static: {
    secret: '23456'
  }
});

var base = new Base();
// base.constructor.staticChain === [Base, Object];
base.static('secret'); // => null; Base.secret

var specialized = new Specialized();
// specialized.constructor.staticChain === [Specialized, Base, Object]
specialized.static('secret'); // => '12345'; Specialized.secret

var even = new EvenMoreSpecialized();
// even.constructor.staticChain === [EvenMoreSpecialized, Specialized, Base, Object]
even.static('secret'); // => '23456'; EvenMoreSpecialized.secret

Answer №1

Creating a static method lookup that explores the prototype and constructor chain to construct a cache for each prototype of static methods found in the constructors can be an interesting task. This solution, compatible with strict TypeScript settings and runnable on Node.js, aims to achieve just that.

An alternative approach could involve implementing the initialization of the static cache as a decorator. However, due to the ongoing development of EcmaScript specifications related to decorators, the TypeScript decorator API remains dynamic and may undergo changes. Once these specifications are finalized, using decorators might offer a more elegant solution.

(Although type safety may be compromised, this method should suffice for handling legacy code)

class Base {
    staticMethod(m:string):(...any:any[])=>any {
        // Check if the static cache already exists; if not, create it.
        if (!("_staticCache" in this)) {
            let statics = Object.create(null);
    
            let collect = (x:any) => {
                let proto = Object.getPrototypeOf(x.prototype);
                
                if (proto)
                    collect(proto.constructor);
                
                for (let prop in x) {
                    statics[prop] = x[prop];
                }
            }
            
            collect(this.constructor);
            
            Object.defineProperty(
                Object.getPrototypeOf(this),
                "_staticCache",
                {enumerable:false,value:statics}
            );
        }

        return (this as any)._staticCache[m];
    }
}

class Foo extends Base {
    static fooOnly(x:string) {
        console.log("fooOnly in foo with arg " + x);
    }

    static fooBar() {
        console.log("fooBar in foo");
    }
}

class Bar extends Foo {
    static fooBar() {
        console.log("fooBar in bar");
    }

    static barOnly() {
        console.log("barOnly in bar");
    }
}

let test = new Bar();

test.staticMethod("fooOnly")("Hello");  // Calls method in foo
test.staticMethod("fooBar")();          // Calls method in bar
test.staticMethod("barOnly")();         // Calls method in bar

Answer №2

It seems like your goal is to set a fixed value and have the class hierarchy return the nearest value, resembling traditional class inheritance behavior.

You didn't specify any need for changing values, so there are a couple of methods to consider: one involving prototypes and the other through assignment. Both achieve the same result in terms of inheritance.

The code snippets here utilize the keyof keyword, which was introduced in TypeScript 2.1.

class Base {
    value: number;
    name : string;
    readonly otherValue: number = 123;
    
    static<K extends keyof this>(key: K): this[K] {
        return this[key];
    }
}

Base.prototype.value = 123;

class InheritFromBase extends Base {
    readonly otherValue: number = 456;
    anotherValue: number;
}

InheritFromBase.prototype.value = 345;

class WorkingExample extends InheritFromBase {}

let base = new Base();

base.static('value') == 'bob';

base.static('name') === 'bob';

base.static('anotherValue');

let ex = new WorkingExample();

ex.static('anotherValue');

base.value = 255;

let result = Base.prototype.value === base.value; 

If you know the possible static keys beforehand and are willing to duplicate inherited values, you can use a separate object approach as well.

interface IStaticOptions {
    optionA?: string;
    optionB?: number;
    optionC?: string;
}

class ThirdBase {
    value: number;
    anotherValue: number;
    staticOptions: IStaticOptions;
    
    static<K extends keyof IStaticOptions>(key: K): IStaticOptions[K] {
        return this.staticOptions[key];
    }

    staticTwo<K extends keyof this>(key: K): this[K] {
        return this[key];
    }
}

This assumes that you are able to replace your current class structure with this new method entirely. If not, then this solution may not be suitable.

Personally, I find the prototype/assignment method from the first example to be preferable.

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

Exploring the synergies between Angular Dragula and utilizing the $index property

Currently, I have implemented an ng-repeat of rows that can be rearranged using Angular Dragula. Despite successful drag-and-drop functionality, the $index in ng-repeat remains constant for each item even after reordering. The screenshot below depicts the ...

Angular: Consistently losing focus on TextArea whenever its model is updated

Currently, I am using the Socket.io library to broadcast changes to all subscribers when data in a textarea is updated. However, I am facing an issue where the textarea loses its focus when it is being updated while in a focus state. My goal is to only upd ...

What is the reason for function components running in multiples of 2 when the state changes?

I need help with a React question that I can't quite figure out. I have a component where new data keeps getting added to the existing data. Initially, it makes sense for two console logs to appear due to Mount and Update. But after that, why do 4 con ...

Inquiry regarding the implementation of DTO within a service layer parameter

I have a query regarding the choice of service layer to use. // 1 export class SomeService{ async create(dto:CreateSomeDto) {} } or // 2 export class SomeService{ async create(title: string, content: string) {} } It appears that most individuals opt ...

Adjusting the dimensions of a control in real-time

In my ASP.NET Web Forms project on Visual Studio 2010, I have three HTML server controls stacked vertically in the browser. The challenge is to dynamically adjust the size of the third control based on the number of items within the second control (which ...

Issue a response with an error message when making an $http request

When using Angular, I encounter a situation where I need to handle error messages returned from an $http request. However, I am unsure of the best approach. In my Express code, I typically handle errors like this: res.send(400, { errors: 'blah' ...

Utilizing RavenDB with NodeJS to fetch associated documents and generate a nested outcome within a query

My index is returning data in the following format: Company_All { name : string; id : string; agentDocumentId : string } I am wondering if it's possible to load the related agent document and then construct a nested result using selectFie ...

What is the appropriate typescript type for an array payload when using the fetch API?

My current method involves using fetch to send URL encoded form data: private purchase = async () => { const { token } = await this.state.instance.requestPaymentMethod(); const formData = []; formData.push(`${encodeURIComponent("paymentTok ...

Using Rails 4 and Ruby 2, I will be implementing JavaScript to successfully attach a PDF document to an

My Rails 4 app, running on Ruby 2, currently triggers the Browser print dialog box using Javascript when a button is clicked. The code snippet for this action is as follows: <%= button 'Print Me', :onclick => 'window.print();return fa ...

Dealing with Exceptions in NestJS and TypeORM: Troubleshooting "Get Status is not a function"

Currently, I am working on an application utilizing NestJS and TypeORM. My main objective is to handle TypeORM errors by using exception filters. However, I have run into a roadblock as I am facing this particular error: (node:345) UnhandledPromiseReject ...

Strange Dojo Error Reporting Issue - 'function is undefined' error incorrectly reported by dojoBuild

Strange issue alert! In development mode, everything seems to be working fine. However, when I run it through dojoBuild, a particular modal is displaying inconsistent behavior. At times, the error message 'undefined' is not a function pops up, s ...

There seems to be an issue with the cookie functionality as it is not

My code for setting and getting Cookies is as follows: $.setCookie = function (c_name, value, exdays) { var exdate = new Date(); exdate.setDate(exdate.getDate() + exdays); var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + e ...

Nuxt.JS is not rendering blocks in the way that I require

I am currently developing a nuxt.js SSR web application and encountered an unusual issue. <div class="container" @mouseenter="hovered = true" @mouseleave="hovered = false"> <transition name="fade"> <div class="class1" v-show="!hovered ...

How can I incorporate the LIKE operator in a query when dealing with PostgreSQL's String array type using TypeORM?

My database backend is PostgreSQL and I have a TypeORM object simplified as follows: @Entity() @Index(['name'], {unique: true}) export class Foo extends BaseEntity { @PrimaryGeneratedColumn('uuid') id: string; @Column() name: st ...

Update the parent element's class style within a targeted div media query

I am facing a challenge where I want the width of my .container element to be 100% when the screen size reaches 900px. The issue is that I have used .container on multiple pages and only wish to change its appearance within the #sub-top div. The terminolo ...

Using HTML and Javascript, create a set of buttons that can dynamically change the CSS styles of specific

I am currently working on an HTML document that contains a table. The table structure is as follows: <table> <tr> <th class="street">Street</th> <th class="city">City</th> <th class=& ...

iOS now supports fully transparent tooltips and modals, offering a sleek and

I am currently working on developing tooltips and modals for an iOS app using react-native. The problem I am encountering is that the background of the tooltips and modals is not transparent, although it appears correctly on the Android version of the app. ...

The NextJs error states that a string cannot be assigned to type Never

'use client' import { FieldErrors, FieldValues, UseFormRegister } from "react-hook-form"; import { BiDollar } from "react-icons/bi"; interface InputProps { id: string; label: string; type?: string; disabled?: ...

Exploring how JavaScript variables behave when used inside a nested forEach loop with an undefined

While inside the second for loop, I am attempting to retrieve the value of this.userId. However, it is returning undefined. Let's take a look at the code snippet below: // The following variable becomes undefined within the second for loop this.userI ...

serving index.html using express and react

I am currently working on an application with Express, which includes a create-react-app. My goal is to display the index.html file located in the public folder of the create-react-app when a user visits the root URL. Below is the code snippet from my inde ...