The Enchantment of Typescript Properties

Currently exploring Typescript and encountered a dilemma. Can Typescript classes be configured to utilize properties added dynamically at runtime? This is the approach I am currently implementing:

interface IObject {
   [key: string]: any;
}

class A {
    private extends: IObject = {};


    constructor() {
        return new Proxy(this, {
            get: (target: any, name: string) => (name in this.extends) ? this.extends[name] : target[name],
        })
    }

    public extend(key: any, argument: object): A {
       this.extends = Object.assign(this.extends, {
           [key]: argument
       });

       return this;
   }
}

This class acts as an expandable context within my application. I can dynamically add properties using

aInstance.extend('property', {a: 1})
and access them like so aInstance.property. In pure JS, it would result in {a: 1}, however, TypeScript throws an error (Property 'property' does not exist on type 'A'.) during this process. Is there a workaround to address this issue? I am aware of using // @ts-ignore, but I prefer avoiding it as it could complicate code maintenance.

Your insights and suggestions are greatly appreciated. Thank you :)

Answer №1

It may be challenging to achieve your exact requirement while maintaining type safety. One approach could involve implementing the following:

class A {
    private extends: Record<string, any> = {};

    constructor() {
        return new Proxy(this, {
            get: (target: any, name: string) => (name in this.extends) ? this.extends[name] : target[name],
        })
    }

    public extend<T extends object>(obj: T): A & T {
       this.extends = Object.assign(this.extends, obj);

       // Although necessary, it is safe because of the expected return type A & T through the proxy
       return this as any;
   }
}

let aInstance = new A()
let extended = aInstance.extend({"test": "foo"})

console.log(extended.test)

In essence, the extend method now produces a type that combines A and T. However, one downside is that only the returned value from the extend function will have the additional property typing.

It's worth considering why runtime type extension is needed in this scenario.

If the property names are known beforehand, consider simply adding them directly to the class. Otherwise, utilizing a Map might be a more suitable solution for this task.

Answer №2

// Here is a code snippet with some mocking to facilitate the execution and testing of your code:

type IObject = {};

class Proxy{
    constructor(obj:any, c:{get:(target:any, name:string) => any}) {

    }
}

class A {
    private extends: IObject = {};

    // It is advisable to implement this method outside the constructor
    // Or alternatively, define A as Class A extends Proxy{}
    getProxy() {
        return new Proxy(this, {
            get: (target: any, name: string) => (name in this.extends) ? this.extends[name] : target[name],
        })
    }

    // The intention behind using the Proxy Class is unclear, 
    // However, utilizing Object.defineProperty might resolve any issues
    public extend(key: any, argument: object): A {
        // To access properties like: aInstance.property
        Object.defineProperty(A.prototype, key, {
            get: function () {
                return argument;
            },
            set: function (value) {
                this[key] = value;
            },
            enumerable: true,
            configurable: true
        });

        // Otherwise, the property will be accessible at:
        // aInstance.extends.property
        this.extends = {...this.extends, ...{
            [key]: argument
        }};

        return this;
    }
}

const aInstance: A = new A();


aInstance.extend('property', { a: 1 });

// Using (aInstance as any) to bypass lint checking restrictions
console.log(aInstance, (aInstance as any).property);

// Output will be:
// A { extends: { property: { a: 1 } } } { a: 1 }

edit

console.log(aInstance['property']); // This will also work fine

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

Uncovering the origins of computed object keys in TypeScript

I am currently working on a project where I need to easily define and use new plugins using TypeScript in my IDE. My folder structure looks like this: src │ ... └── plugins └── pluginA | index.ts └── pluginB | index. ...

Creating a split hero section view using a combination of absolute/relative CSS techniques, Tailwind, and React

I'm in the process of creating a website using Nextjs, React, and TailwindCSS, and I aim to design a Hero section that resembles the one on the following website. https://i.sstatic.net/tq3zW.png My goal is to: Have a text title and buttons on the l ...

XState TypeScript - utilizing the Interprete Service

I have developed a login system using a combination of TypeScript, xState, and React for the UI. The machine I have created includes the following configuration: import { LoginResponse } from 'async/authentication/responseModel'; import Data fro ...

Dealing With HttpClient and Asynchronous Functionality in Angular

I've been pondering this issue all day. I have a button that should withdraw a student from a class, which is straightforward. However, it should also check the database for a waiting list for that class and enroll the next person if there is any. In ...

The Http.get() function is running smoothly, yet encountering issues when it comes to functionality within the build (Release/Debug)

Currently, I am facing an issue with fetching data from a simple API. Strangely, the functionality works perfectly fine when testing in ionic serve (browser). However, upon building the app, the HTTP call fails to work. Below is the snippet of my code: th ...

Issue with create-react-app and Emotion.js: Uncaught ReferenceError: jsx is undefined

I am currently attempting to incorporate emotion.js into my create-react-app project using TypeScript. I followed the steps outlined in the documentation, which involved adding @emotion/core, importing {jsx, css} from '@emotion/core';, and includ ...

Retrieve an Array Containing a Mix of Objects and Functions in Typescript

Let's address the issue at hand: I spent several months working with a custom React Hook using plain JavaScript, and here is the code: import { useState } from 'react'; const useForm = (initialValues) => { const [state, setState] = ...

Hidden back navigation strategy in AngularJS 2 with location strategy

After creating a custom LocationStrategy to disable browser location bar changes, I am now looking to integrate smaller apps into various web pages without affecting the browser's location. While navigation works smoothly with this new strategy, I am ...

Instructions on resolving the issue: The type 'string | ChatCompletionContentPart[] | null' cannot be assigned to type 'ReactNode'

I've been working on my first Saas App, similar to a ChatGPT, using NextJs with the OpenAI Api. Most of the development was based on a YouTube tutorial until I encountered two errors caused by an update in the OpenAI version. Despite trying various so ...

Join and Navigate in Angular 2

Attempting to retrieve information from a JSON file has been an issue for me. Here is the code snippet: ngOnInit() { this.http.get('assets/json/buildings.json', { responseType: 'text'}) .map(response => response) .subsc ...

The issue of assigning a file as a prop in React TypeScript: ("True" type cannot be assigned to "ChangeEventHandler<HTMLInputElement>")

I'm currently developing an application using reactjs My goal is to implement a feature that allows file uploads passed as a prop from a child component to a parent component Child Component const RegisterIndividual: React.FC< { upload_id_card: R ...

Creating a numeric sequence based on the date of a corresponding transaction - a step-by-step guide

INTRO I built an e-commerce app with TypeScript and Sequelize ORM. In the app, I have a table that generates sequential invoice numbers based on the current day. CREATE TABLE `dm_generate_trx` ( `id` int NOT NULL AUTO_INCREMENT, `date` date NOT NULL, ...

Tips for maintaining type information while setting values in a class membership declaration

In my current class implementation, I have defined the following: export class HavenHandler implements IHavenHandler { opts: { auto: true handleGlobalErrors: true; revealStackTraces: true } // compared to: opts: { auto: boolean ...

Utilizing Regular Expressions as a Key for Object Mapping

Currently, I am facing a challenge in mapping objects with keys for easy retrieval. The issue arises when the key can either be a string or a RegExp. Assigning a string as a key is simple, but setting a regex as a key poses a problem. This is how I typica ...

What is the role of authguard in securing routes?

When developing an application, I encountered the need to implement authorization to protect routes using AuthGuard. However, I now face the challenge of securing child routes based on a role system obtained from the backend during login. For example, if t ...

"Classes can be successfully imported in a console environment, however, they encounter issues when

Running main.js in the console using node works perfectly fine for me. However, when I attempt to run it through a browser by implementing an HTML file, I do not see anything printed to the console. Interestingly, if I remove any mentions of Vector.ts fro ...

Challenges with Loading JSON Dynamically in Next.js using an NPM Package

In my TypeScript project, I have implemented a functionality where a json configuration file is dynamically loaded based on an enum value passed as a parameter to the getInstance function in my PlatformConfigurationFactory file. public static async getIn ...

Angular allows for creating a single build that caters to the unique global style needs of every

Currently, I am working on a project for two different clients, each requiring a unique style.css (Global CSS). My goal is to create a single production build that can be served to both clients, who have different domains. I would like the global style t ...

Issue with Angular ngFor not updating radio button value when ngModel is set

Hello, I am fairly new to working with Angular and could really use some assistance with a problem I've run into. Essentially, I am receiving an array of objects from an API like this: [{name: "abc", score: 2},{name: ""def, score: ...

Dynamic table row that expands to show additional rows sourced from various data sets

Is there a way to expand the table row in an angular-material table when it is clicked, showing multiple sets of rows in the same column as the table? The new rows should share the same column header but not necessarily come from the same data source. Whe ...