Can dynamic getters and setters be implemented in TypeScript?

I've recently ventured into typescript and am currently working on converting our application from es2016 to TypeScript. My current task involves creating a class with data properties and ensuring each element from the data object is accessible as a class property.

However, I'm facing a challenge with this JavaScript code snippet:

for(let key in this.data) {
  Object.defineProperty(this, key, {
    get: function(value:any) { return this.data[key]; },
    set: function(value:any) {
      if (this.data[key] !== value) {
        this.data[key] = value;
        this.updatedKeys.push(key);
      }
    },
  });
}

Implementing getter/setters in TypeScript is straightforward, but my confusion stems from whether I can generate them dynamically.

interface IData {
  id: number;
  [propName: string]: any;
}

class Model  {
  
  protected updatedKeys:string[] = [];
  
  baseUrl:string = null;
  data:IData;
  fields:IData;
  
  constructor(data:IData={id:null}, fields:IData={id:null}) {
    super();
    this.data = data;
    this.fields = fields;
    
    for(let key in this.data) {
      Object.defineProperty(this, key, {
        get: function(value:any) { return this.data[key]; },
        set: function(value:any) {
          if (this.data[key] !== value) {
            this.data[key] = value;
            this.updatedKeys.push(key);
          }
        },
      });
    }
  }
}

tsc -t ES2016 --lib "es2016","dom" models.ts

This command returns an error:

models.ts(33,40): error TS2345: Argument of type '{ get: (value: any) => any; set: (value: any) => void; }' is not assignable to parameter of type 'PropertyDescriptor & ThisType<any>'.
  Type '{ get: (value: any) => any; set: (value: any) => void; }' is not assignable to type 'PropertyDescriptor'.
    Types of property 'get' are incompatible.
      Type '(value: any) => any' is not assignable to type '() => any'.

I'm currently unsure how to resolve this issue.

Answer №1

Big thanks to https://github.com/epicgirl1998 for helping me discover the solution. Here's the fix that was provided:

The issue lies in the getter method having a value parameter, even though getters do not receive any values.

I replaced it with get: function() { return this.data[key]; }, which resolved the error. Additionally, there is an unnecessary super call in the class unless it extends another class.

It's important to note that 'this' inside the accessors does not refer to the class instance. Using arrow functions should resolve this issue.

Please give this a try:

interface IData {
  id: number;
  [propName: string]: any;
}

class Model  {
  
  protected updatedKeys:string[] = [];
  
  baseUrl:string = null;
  data:IData;
  fields:IData;

  constructor(data:IData={id:null}, fields:IData={id:null}) {
    
    this.data = data;
    this.fields = fields;
    
    for(let key in this.data) {
      Object.defineProperty(this, key, {
        get: () => { return this.data[key]; },
        set: (value:any) => {
          if (this.data[key] !== value) {
            this.data[key] = value;
            this.updatedKeys.push(key);
          }
        },
      });
    }
  }
}

Answer №2

When working with TypeScript, it is common to not create objects dynamically with methods and properties. Instead, you can either instantiate classes or define the data structure using an interface.

If your goal is simply to convert loaded JSON data into typed data, you can utilize an interface to specify the schema of your JSON object.

An example interface outlining actor properties

interface Actor {
    name: string;
    height: number;
}

Fetching generic JSON data

let data : any = getSingleActorData();

Typing the actor to an interface and storing it in an array

let actorData : Actor[] = [];
actorData.push(data as Actor);

Your IDE will now provide auto-completion for accessing the name and height of the actor variable:

console.log(actorData[0].name);

If you do require a complete 'object' with getters and setters, you can define an Actor class and then instantiate it with the loaded data:

class Actor {
    private _name:string;
    private _height:string;
    get name {}
    set name {}
    get height {}
    set height {}
    constructor(name:string, height:number){
    }
}

You can then assign your JSON data to an instance of the actor class:

actorData.push(new Actor(jsondata.name, jsondata.height));

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

"Implementing self-referencing mongoose models in Typescript: A step-by-step guide

I have a model: const message = new mongoose.Schema({ id: { type: ObjectId, required: true }, text: { type: String }, replies: [message] }); Looking to create a document structure like this: { "id": 1, "text": "Main Message", "replies": [ ...

I obtained the binary tree output in the form of an object. How can I extract the values from this object and store them in an array to continue working on

Issue Statement In this scenario, you have been presented with a tree consisting of N nodes that are rooted at 1. Each node in the tree is associated with a special number, Se. Moreover, each node possesses a certain Power, which is determined by the count ...

Cannot access Injectable service in Angular2

In the angular2 application, there is a service named HttpClient. The purpose of this service is to include an authorization header in every request sent by the application to endpoints. import { Injectable } from '@angular/core'; import { He ...

Download pictures from swift into typescript with the help of share extensions

Currently, I am working on setting up Share Extensions in my ionic3 application. To begin with, I followed these steps: Firstly, I built the app and then launched it in Xcode. After that, I added a Share Extension by navigating to File -> New -> Ta ...

The situation I find myself in frequently is that the Angular component Input

There seems to be an issue with a specific part of my application where the inputs are not binding correctly. The component in question is: @Component({ selector : 'default-actions', templateUrl : './default.actions.template.html&a ...

Ignore one specific file when importing all files in Angular 7

In my Angular 7 project, I am utilizing C3 and importing all the necessary files at the beginning of my .ts component file using a wildcard. import * as c3 from 'c3'; While this method works well overall, I encountered an issue where my CSS ove ...

Obtaining the initial value from an Observable in Angular 8+

Initially, I have a page form with preset values and buttons for navigating to the next or previous items. Upon initialization in ngOnInit, an observable provides me with a list of 3 items as the starting value - sometimes it may even contain 4 items. Ho ...

Create a debounced and chunked asynchronous queue system that utilizes streams

As someone who is new to the concept of reactive programming, I find myself wondering if there exists a more elegant approach for creating a debounced chunked async queue. But what exactly is a debounced chunked async queue? While the name might need some ...

Tips for defining a distinct series of key-value pairs in typescript

Having experience with a different language where this was simple, I am finding it challenging to articulate a sequence of pairs: Each pair is made up of two basic elements (such as strings or numbers) Each element may appear multiple times within the lis ...

"Scanner in Java: Capturing Input from an Object

I have a method that utilizes an object from the Region class. Since this method is called by another method, I need to input that object so the method can be applied. public void changeBusinessPermitTile(Region region){ if(getRegion()==null) { ...

Error encountered when trying to update tree structure in TypeScript with new data due to incorrect array length validation

I have encountered an issue with my tree data structure in TypeScript. After running the updateInputArray(chatTree); function, I am getting an “invalid array length” error at the line totalArray.push(iteratorNode.data);. Furthermore, the browser freeze ...

How to implement a toggle button in an Angular 2 application with TypeScript

Currently, I'm working with angular2 in conjunction with typescript. Does anyone know how to generate a toggle button using on - off?. ...

There is a chance that the object could be 'undefined' when attempting to add data to it

I created an object and a property called formTemplateValues. I am certain that this property exists, but I am getting an error message saying: "Object is possibly 'undefined'". It should not be undefined because I specifically created it. Why am ...

The issue persists wherein getBoundingClientRect fails to provide the accurate value following a resize

I have implemented a custom directive that appends a dropdown to the body when it is displayed. The logic functions correctly when executed within the ngAfterViewInit lifecycle, but I encounter issues when attempting to use the same logic within the wind ...

What is the process for using infer to determine the return type of a void function?

I am trying to gain a better understanding of how to utilize the infer keyword in TypeScript. Is this an appropriate example demonstrating the correct usage of infer? I simply want to infer the return type of the function below: const [name, setName] = u ...

Performing simultaneous document queries within a single API in MongoDB

I am currently working with an API written in typescript and attempting to execute parallel queries for the same document by using Promise.allSettled. However, I have noticed that it is performing poorly and seems to be running sequentially instead of in p ...

Error in TypeScript: The type 'Ticket[] | undefined' is not recognized as an array type

How can I add a new item to an array of objects in React state using TypeScript? Here is the code snippet in question: export type AppState = { tickets?: Ticket[], } export type Ticket = { id: string, title: string; } export type ApiC = { ...

Creating an interface and setting a default value

I am exploring the use of interfaces in my models and want to establish a default value for them. export interface IPerson { id: string; name: string; } class Person implements IPerson { id = ''; name = 'John'; } export cla ...

Testing Angular 16 Component with Jasmine Spy and callFake Strategy

I've encountered an issue while trying to test my component unit. The problem arises when I call the product-list.component.ts from my product.service.ts. While my product.service.spec.ts is successful, the product-list.component.spec.ts fails as the ...

Using the useRef validation can lead to errors when trying to reference the current value against an input

Currently, the code is functioning well but an error alert from Typescript needs to be addressed. A warning pops up regarding the use of ref.current.value. ERROR 1. TS18048: 'ref.current' is possibly 'undefined'. To tackle this issue, ...