Dynamic getter/setter in Typescript allows for the creation of functions

I'm facing a challenge in making Typescript automatically infer types for dynamically created getter and setter functions. In my code, I have a class called MyClass which contains a map of containers:

type Container = {
    get: () => Content
    set: (value: Content) => void
}

type ContainersMap = { [key: string]: Container }

class MyClass {
    containers: ContainersMap

    constructor(names: string[]) {
        this.containers = {} as ContainersMap

        names.forEach(name => {
            Object.defineProperty(this.containers, name, {
                get() { return read(name) },
                set(value) { write(name, value) }
            })
        })

        Object.freeze(this.containers)
    }
}

In another part of my code, I want to utilize it like this:

let someContent: Content = {/* some content */}

let myClass = new MyClass(['first', 'second'])

// I'm encountering a type error here because TypeScript is interpreting it as an attempt to assign Content to Container
myClass.containers['first'] = someContent 

I came across a potential solution where I defined

type ContainersMap = { [key: string]: Content }
, but I'm not fully satisfied with this approach as it doesn't accurately represent the intended type of ContainersMap.

Is there a more appropriate way to implement this?

Answer №1

When working with classes, it's important to separate the public interface from the implementation details.

type Container = {
    get: () => Content
    set: (value: Content) => void
}

This definition specifies an object with properties get and set. You can use

myClass.containers.first.set(someContent)
, but not
myClass.containers.first = someContent
because myClass.containers.first should be of type Container, not Content.

The get and set methods are simply part of how your class implements the desired interface – making myClass.containers.first a readable and writable property of type Content. The contract for this interface is straightforward.

type ContainersMap = { [key: string]: Content }

or when using known keys:

type ContainersMap<K extends string> = { [key in K]: Content } // same as Record<K, Content>

To allow optional properties without initial values, considering making them optional.


A generic class is needed for ContainersMap to recognize specific keys. Without knowledge of your code's read and write methods, leaving them unchanged is advisable. This example illustrates the concept:

class MyClass<K extends string> {
    containers: ContainersMap<K>

    constructor(names: K[]) {
        this.containers = {} as ContainersMap<K>

        names.forEach(name => {
            Object.defineProperty(this.containers, name, {
                get() { return read(name) },
                set(value) { write(name, value) }
            })
        })

        Object.freeze(this.containers)
    }
}

The Container type is no longer necessary.


This setup allows access to defined properties:

let myClass = new MyClass(['first', 'second'])

myClass.containers.first = someContent;

console.log(myClass.containers.first);

However, attempting to access other properties will result in an error:

// Error: Property 'third' does not exist on type 'ContainersMap<"first" | "second">'
myClass.containers.third = someContent;

Typescript 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

Can TypeScript support promise chaining in a functional way?

It appears that in the realm of JavaScript, one has the capability to execute: function extendPromise(promise) { return promise.then(new Promise(() => {})); } However, when incorporating types into the mix, such as function extendTypeScriptPromis ...

Angular/NestJS user roles and authentication through JWT tokens

I am encountering difficulties in retrieving the user's role from the JWT token. It seems to be functioning properly for the ID but not for the role. Here is my guard: if (this.jwtService.isTokenExpired() || !this.authService.isAuthenticated()) { ...

Coloring intersected meshes in three.js will recolor every mesh in the scene

My attempt to change the color of a mesh on mouse hover is not functioning as expected. Instead of coloring only one mesh red, every single mesh is being filled with the color. Upon inspecting the intersected objects during debugging, it shows only one el ...

Running a TypeScript file on Heroku Scheduler - A step-by-step guide

I have set up a TypeScript server on Heroku and am attempting to schedule a recurring job to run hourly. The application itself functions smoothly and serves all the necessary data, but I encounter failures when trying to execute a job using "Heroku Schedu ...

Issue with TypeScript version 4.2.1 - The Array.some() method does not support a function that returns a boolean

I encountered a TypeScript error that goes as follows: https://i.sstatic.net/RoGER.png The complete error message reads: Supplied parameters do not match any signature of call target: parameter type mismatch. > Parameter 'Predicate' should ...

Separate the string by commas, excluding any commas that are within quotation marks - javascript

While similar questions have been asked in this forum before, my specific requirement differs slightly. Apologies if this causes any confusion. The string I am working with is as follows - myString = " "123","ABC", "ABC,DEF", "GHI" " My goal is to spli ...

Transforming Typescript Strings into ##,## Format

As I've been using a method to convert strings into ##,## format, I can't help but wonder if there's an easier way to achieve the same result. Any suggestions? return new Intl.NumberFormat('de-DE', { minimumFractionDigits: 2, max ...

Verify enum values within controller function

I am dealing with a query parameter in my REST API that should be restricted to specific values according to an enum type. I need to find a way to handle a "Bad Request" error if the client provides any value outside of this enum. Here is what my enum loo ...

Stop MatDialog from closing automatically when clicked outside while there are unsaved changes

Is there a way to prevent closing when there are pending changes without success? this.dialogRef.beforeClosed().subscribe(() => { this.dialogRef.close(false); //some code logic //... }); The setting disableClose on MatDialog must remain as false ...

Using React and TypeScript, open the initial tab from a mapped array with an accordion component

{accordion.map(accordionItem => ( <AccordionItem key={accordionItem.title} text={accordionItem.text} title={accordionItem.title} ></AccordionItem> ...

What is a more efficient method for incorporating optional values into an object?

Currently, I am utilizing the optional addition feature in this way: ...(!!providerId && { providerId }), ...(!!practiceId && { practiceId }), Is there a more elegant shorthand method to replace this logic, such as: yield createRemark ...

Synchronize Docker volumes

Hey there! I've been trying to dive into learning Docker, but I'm having trouble syncing the host and container using volumes when making changes and saving code (specifically using npm run dev). Every time I need to restart docker-compose up --b ...

Binding an ID to an <ion-textarea> in Ionic 3

Can an ID be assigned to an ion-textarea? For example: <ion-textarea placeholder="Enter your thoughts" id="thoughtsBox"></ion-textarea> Thank you very much ...

JavaScript heap exhausted while running Docker container

Typically, I start my application by running npm run dev. The package.json file contains a script like the one below: "scripts": { "dev": "nodemon server.ts", } Everything is working fine in this setup. I have cr ...

Error in Typescript: The property 'a' is not defined in the type 'A'

Here is an example of the different types I am working with: type Place = { address: string } type Location = { latLng: string } type User = { name: string } & (Place | Location) When attempting to parse the data using this structure, I enco ...

The best approach to typing a FunctionComponent in React with typescript

I'm diving into the world of React and TypeScript for the first time. Could someone verify if this is the correct way to type a FunctionComponent? type ModalProps = { children: ReactElement<any>; show: boolean; modalClosed(): void; }; co ...

Initial state was not properly set for the reducer in TypeScript

Encountered an error while setting up the reuder: /Users/Lxinyang/Desktop/angular/breakdown/ui/app/src/reducers/filters.spec.ts (12,9): error TS2345: Argument of type '{}' is not assignable to parameter of type '{ selectionState: { source: ...

The export 'ChartObject' is not available in highcharts

Trying to integrate highcharts into my Angular 4 project has been a bit challenging as I keep encountering the following error: highcharts has no exported member 'ChartObject' I have experimented with different options such as angular-highchart ...

Understanding how to infer the type of a function when it is passed as an argument

Looking at the images below, I am facing an issue with my function that accepts options in the form of an object where one of the arguments is a transform function. The problem is that while the type of the response argument is correctly inferred for the e ...

Undefined TypeScript Interface

Here's my situation: public retrieveConnections() : IUser[] { let connections: IUser[]; connections[0].Id = "test"; connections[0].Email = "asdasd"; return connections; } I know this might be a dumb question, but why is connecti ...