The property decorator in a TypeScript class behaves similarly to a static property

I have recently implemented a class using a property decorator that sets a flag within the class whenever a decorated property is updated. However, I am encountering an issue when trying to copy values from one instance of the class to another. It seems like setting the value of a property on one object also changes the value on another object, almost as if the property is static. I am relatively new to JavaScript and TypeScript, so I must have overlooked something. Can anyone help me identify where I went wrong?

After running the code snippet below, the following output is logged:

Setting propNum from undefined to 0
testclass.ts:18 Setting propNum from 0 to 123
test.spec.ts:13 t1.propNum = 123
test.spec.ts:14 t2.propNum = 123

The expected behavior is for t1.propNum to remain zero.

Decorating Function

//
// Property decorator used to automatically set a dirty flag for any decorated property
//
function testProperty( target: any, key: string ) {

    // Initialize property value
    var _val = this[key];

    // Define property getter
    function getter() {
        return _val;
    };

    // Define property setter
    function setter( newVal ) {

        if ( _val != newVal ) {
            console.log( `Setting ${key} from ${_val} to ${newVal}` );
            _val = newVal;
            this._dirty = true;
        }
    };

    //
    // Delete original property and re-define it with custom getter & setter
    //
    if ( delete this[key] ) {

        // Create new property with customized getter and setter
        Object.defineProperty( target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}

TestClass Definition

export class TestClass {

    private _dirty: boolean;

    @testProperty
    public propNum: number = 0;


    constructor() {
        this._dirty = false;
    }

    public copyFrom( tc: TestClass ) {
        this.propNum = tc.propNum;
    }
}

Testing Code

describe( 'Copy Class Test', () => {

    it( 'Copy Test', () => {

        var t1 = new TestClass();
        var t2 = new TestClass();

        t2.propNum = 123;

        console.log( `t1.propNum = ${t1.propNum}` );
        console.log( `t2.propNum = ${t2.propNum}` );

        expect( t1.propNum ).toBe( 0 );

        t1.copyFrom( t2 );

        expect( t1.propNum ).toBe( 123 );
    });
});

Answer №1

The main issue at hand is the fact that both the getter and setter are sharing a common variable instead of retrieving a value specific to each instance.

Essentially, it can be likened to this scenario:

function TestClass() {
}

var value;

Object.defineProperty(TestClass.prototype, "propNum", {
    get: function() { return value; },
    set: function(val) { value = val },
    enumerable: true,
    configurable: true
});

This leads to the following outcome:

var x = new TestClass(), y = new TestClass();
x.propNum = 2;
x.propNum === y.propNum; // true, as they are both pointing to the same variable

The second issue arises from this[key] referencing a property on the global object.

You might want to consider something in this vein (untested code):

function testProperty( target: Object, key: string ) {
    const privateKey = "_" + key;

    function getter() {
        return this[privateKey];
    }

    function setter( newVal: any ) {
        if ( this[privateKey] != newVal ) {
            console.log( `Changing ${key} from ${this[privateKey]} to ${newVal}` );
            this[privateKey] = newVal;
            this._dirty = true;
        }
    }

    Object.defineProperty( target, key, {
        get: getter,
        set: setter,
        enumerable: true,
        configurable: true
    });
}

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 is the method to retrieve the name of a clicked button using JavaScript?

There are 2 <div> elements containing a minimum of 81 buttons each, all having the same class. However, they have unique ids and names. I am currently experimenting with ways to display an alert message showing the name of the button that is being ...

Utilize the JSSOR Slider to dynamically pull images from an online album, such as by integrating with an RSS Feed

My main motivation for exploring this possibility is the negative impact that loading images into the slider has on my website's performance. Is there a method to import images from an externally hosted album, such as Google Picasa albums (or any othe ...

Tips for obtaining the retrieved URL from an ajax call

How can I extract only the returned URL from an ajax request? I have tried implementing it like this: $.ajax({ type: "GET", dataType : "jsonp", async: false, url: $('#F ...

Is it necessary to include a promise in the test when utilizing Chai as Promised?

Documentation provided by Chai as Promised indicates the following: Note: when using promise assertions, either return the promise or notify(done) must be used. Examples from the site demonstrate this concept: return doSomethingAsync().should.eventua ...

What is the best way to showcase a chart using jquery?

Is there a way to incorporate trendlines or target lines in highcharts similar to fusion chart? I have been able to draw them successfully in fusion charts. Check out this fiddle link: http://jsfiddle.net/Tu57h/139/ I attempted to recreate the same in hi ...

Kartik's gridview in yii2 has a unique feature where the floating header in the thead and tbody are

I'm looking to create a table gridview with a floating header, where the tbody and thead are not the same. The image appears after refreshing the page, before the modal is refreshed. After refreshing the modal, this modal is inside pjax and it sets t ...

Unable to fetch source for HTML img tag

I am struggling with using jQuery to retrieve the src attribute of an image when it is clicked. Unfortunately, my current code does not output anything to the console and returns undefined when I try to manipulate it in the browser console. I do not have m ...

The plumber encountered an issue and triggered a custom error function, resulting in an error in the 'plumber' plugin: Error message: Unable to connect to undefined

Currently, I am developing a Wordpress theme starter application using node, gulp, and handlebars to generate templates. I am running into an issue with the integration of plumber, notify, and gulp-sass plugins. If you are interested in checking out my w ...

What is the process for importing specific modules from jQuery?

When working with webpack or browserify, what is the specific procedure required to import only the essential modules from jQuery as mentioned here? import {core, dimensions} from 'jquery' Unfortunately, this approach does not seem to be effect ...

The concept of promises and futures in JavaScript bears a resemblance to the functionality present

Is there a JavaScript library that offers promises and futures with syntax similar to C++? We need to use them in webworkers without a callback interface. I want the webworker to pause on a future and resume when the UI thread assigns a value to it. I ha ...

Clicking on checkboxes using jQuery

Here is my current code snippet and the task I am currently working on. The objective is to check the corresponding checkbox if json[i].enabled is true, and leave it empty if not. function createTable(json) { var element = ""; var i; ...

Retrieving the initial element within a JSON structure

Let's dive right in. I am receiving a JSON object that contains another object within it, like this: function getSummonerInfo(summonerName, region) { LolApi.Summoner.getByName(summonerName, region, function(err, summoner) { if(!err) { ...

Using Generators with the for...of loop in Typescript

I am currently facing an issue with Typescript when trying to compile a generator-loop that works perfectly in a modern browser. The code snippet in question is: /** Should print "x= 1 y= 2" **/ function* gen() { yield [1, 2] } for (const [x, y] of gen()) ...

Tips for incorporating a JavaScript file directly into your HTML code

I'm working with a compact javascript file named alg-wSelect.js, containing just one line of code: jQuery('select.alg-wselect').wSelect(); This script is used by a wordpress plugin. My question is whether it's feasible to incorporate th ...

Socket.io operates individually with each user

Showing a basic web-chat using socket.io. Node.js code: io.on('connection', function(socket) { // Sends 'hello world' message to all users socket.emit('send:message', { text: 'hello world' }); ...

Child Components in React: Managing State and Events

When managing the state of parent objects using callback functions in child components, I encountered an issue. The code below works well with just one variable, but when dealing with Objects, I get an error while entering values into the textarea. Error ...

Responsive design element order rearrangement

My code example is as follows: <div class="info-container"> <span class="item1">item1</span> <a class="item2" href="#">item2</a> <a class="item3" href="#">item3</a> </div> I want to rearran ...

ShadowBox not displaying Vimeo videos

I can't figure out why my Vimeo videos are not appearing in a Shadowbox. I have followed the steps I know to be the simplest, which involve copying the example directly from the github page and then updating the shadowbox paths to match the locations ...

To prevent the background window from being active while the pop-up is open

I have a link on my webpage that triggers a pop-up window, causing the background to turn grey. However, I am still able to click on other links in the background while the pop-up is open. I tried using the code document.getElementById('pagewrapper&ap ...

Determine the presence or absence of data in an Angular Observable

Here is an example of how I am making an API call: public getAllLocations(): Observable<any> { location = https://v/locations.pipe(timeout(180000)); return location; } In my appl ...