The concept of 'this' remains undefined when using a TypeScript Property Decorator

I've been delving into TypeScript decorators focusing on properties, and I crafted the following snippet inspired by various examples:

decorator.ts

export function logProperty(target: any, key: string) {

  let val = this[key];

  const getter = () => {
    console.log(`Get: ${key} => ${val}`);
    return val;
  };

  const setter = (newVal) => {
    console.log(`Set: ${key} => ${newVal}`);
    val = newVal;
  };

  if (delete this[key]) {
    Object.defineProperty(target, key, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  }
}

main.ts

import { logProperty } from './decorators';

class Person {
  @logProperty
  firstName: string;

  @logProperty
  lastName: string;

  constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

const foo = new Person('Foo', 'Bar');

My challenge lies in encountering the following error when attempting to execute this setup:

TypeError: Cannot read property 'firstName' of undefined

The underlying issue appears to be related to the undefined value for this. Can anyone help me identify what's missing?

For clarifications, my tsconfig.json includes:

"target": "es5"
"experimentalDecorators": true
"strict": false

UPDATE 8/27 The problem seemingly arises only when the decorator is placed in a separate .ts file. The error doesn't manifest when everything is consolidated within a single file. Could it be an oversight in how this is being interpreted?

Answer №1

Summary: Initially, OP's configuration didn't work for unknown reasons, but now it seems to be functioning perfectly after some thorough testing.

Hypothesis

I speculate that the issue might have been caused by inadvertently using the wrong tsconfig. Upon reviewing the repository's tsconfig, everything appears correct. Could there have been interference from another configuration file during those runs? I noticed that there were no automated tests present.

Experimentation

Encountering a similar problem today, I conducted a quick test utilizing OP's model. I extracted compiler options from the official documentation.

decorators.ts

export function logProperty(target: any, key: string) {

    let val = this[key];

    const getter = () => {
        console.log(`Get: ${key} => ${val}`);
        return val;
    };

    const setter = (newVal) => {
        console.log(`Set: ${key} => ${newVal}`);
        val = newVal;
    };

    if (delete this[key]) {
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}

main.ts

import { logProperty } from './decorators';

class Person {
    @logProperty
    firstName: string;

    @logProperty
    lastName: string;

    constructor(firstName: string, lastName: string) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

const foo = new Person('Foo', 'Bar');

function logProperty2(target: any, key: string) {

    let val = this[key];

    const getter = () => {
        console.log(`Get: ${key} => ${val}`);
        return val;
    };

    const setter = (newVal) => {
        console.log(`Set: ${key} => ${newVal}`);
        val = newVal;
    };

    if (delete this[key]) {
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}

class Person2 {
    @logProperty2
    firstName: string;

    @logProperty2
    lastName: string;

    constructor(firstName: string, lastName: string) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

const foo2 = new Person2('Foo', 'Bar');

index.ts

Outcomes

To observe the complete build process, visit Travis.

Robust tsconfig

Based on the outcomes, it seems that this tsconfig is acceptable.

Concluding Remarks

  • I may not have tested all potential compiler options thoroughly. If desired, I can update the repository with additional tests later on.
  • The issue encountered by OP still perplexes me, as I haven't found a definitive solution.
  • I also verified the TypeScript version used. While OP utilized 2.4.2, I employed 2.7.2. To rule out version-related issues, I downgraded my version as well.

Answer №2

The reason why 'this' is not defined is due to improper configuration of the property descriptor.

Instead of using:

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

You should consider doing something like this:

Object.defineProperty(target, key, {
      get() {
         // 'this' will be defined here
      },
      set(value: any) {
         // 'this' will be defined here
      },
      enumerable: true,
      configurable: true
    });

I encountered the same issue and adopting the solution provided above resolved it for me.

Answer №3

Here is a helpful tip:

Instead of using the following code snippet:

function logProperty(target: any, key: string) {

 ...
}

Consider using this alternative approach:

const logProperty = (target: any, key: string) => {
 ...
}

This change utilizes => which scopes 'this' just outside, allowing it to be accessed. I hope you find this useful!

Answer №4

I took inspiration from allen wang's code and modified it to create my own solution:

export const logProperty = (target: any, key: string) => {
    let val = this?[key]: '';

    const getter = () => {
        console.log(`Get: ${key} => ${val}`);
        return val;
    };

    const setter = (newVal) => {
        console.log(`Set: ${key} => ${newVal}`);
        val = newVal;
    };

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

Answer №5

I encountered a similar issue, but with some strict conditions in place.

This was how my code looked:

namespace StringUtil {
  export function validate(...) {...} 
}

export function mv_string(minLen: number, maxLen: number, allowNull?: boolean, errCode?: string, errMsg?: string) {
    return function (target: any, propertyKey: string) {
        let cls = target.constructor as Function;
        createValidator(cls, propertyKey, StringUtil.validate, [minLen, maxLen, allowNull], errCode, errMsg);
    };
}

class MyCls {
    @mv_string
    myProp: string;
}

Previously, I had been using typescript 3.7.0+ with an older ts-node version, and everything worked smoothly whether running through ts-node or webpack && node xxx.

However, after upgrading to typescript 4.7.0+ with a newer ts-node 10.0.0+ version, the webpack & node run still went well, but ts-node | ts-node-dev started giving me a runtime error:

/Users/xxx/xxx/node_modules/reflect-metadata/Reflect.js:553
                var decorated = decorator(target, propertyKey, descriptor);
                                ^
TypeError: Cannot read properties of undefined (reading 'validate')
    at /Users/xxx/xxx/src/xxx/my-xxx-validator.ts:55:54
    at DecorateProperty (/Users/xxx/xxx/node_modules/reflect-metadata/Reflect.js:553:33)
    at Reflect.decorate (/Users/xxx/xxx/node_modules/reflect-metadata/Reflect.js:123:24)
    at __decorate (/Users/xxx/xxx/src/my-xxx-tscode.ts:4:92)
    at Object.<anonymous> (/Users/xxx/xxx/src/my-xxx-tscode.ts:6:5)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Module.m._compile (/Users/xxx/xxx/node_modules/ts-node/src/index.ts:839:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Object.require.extensions.<computed> [as .ts] (/Users/xxx/xxx/node_modules/ts-node/src/index.ts:842:12)
    at Module.load (node:internal/modules/cjs/loader:981:32)

The error primarily pointed out that StringUtil was undefined when using StringUtil.validate.

I suspect the issue could be related to what @CJ Harries mentioned earlier, although I'm not entirely certain. It seems odd that the namespace would be compiled as an object while working with decorators via ts-node. Perhaps the upgrade introduced some breaking changes in this regard?

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

"Troubleshooting: NuxtJs vue-flip feature stuck on front side, not rotating to

I have recently installed the vue-flip plugin in a NuxtJs (VueJS) project that I created using the command: npx create-nuxt-app <project-name>. In the index.vue file, I simply added: <vue-flip active-click="true"> <div slot="front"> ...

When render returns another component, React does not invoke componentWillMount

Here is my code setup: const Dashboard = React.createClass({ getInitialState(){ return { user: JSON.parse(localStorage.getItem('user')) }; }, componentWillMount(){ var self = this; $.get({ url: 'http://127 ...

Incorporating Common Types for Multiple Uses

Is there a way to efficiently store and reuse typings for multiple React components that share the same props? Consider the following: before: import * as React from 'react'; interface AnotherButtonProps { disabled?: boolean; onClick: (ev ...

Is there a straightforward way to upload a folder in a ReactJS application?

I am searching for a way to upload a folder in ReactJS. I have a folder with .doc and .docx files in it, and I want the user to be able to upload the entire folder when they click the browse button. I need to prevent the user from selecting individual fi ...

Updating a component in Angular 4.3.1 from within an observable callback

My Project Journey I am currently immersing myself in learning Angular by working on a personal project: developing a game that involves routing, services, and more. One of the requirements is to hide the header on the landing page (route for '/&apos ...

Implementing Passport authentication for Steam, transitioning from Express to NestJS

I have embarked on the task of transitioning an express project to nestjs. How can I achieve the same functionality in Nestjs as shown in the working code snippet from Express below? (The code simply redirects to the Steam sign-in page) /* eslint-disable s ...

Why Won't My PHP Form Submit on a Bootstrap Website?

I'm struggling with validation and need some assistance. I have a Bootstrap form embedded within an HTML page and a PHP script to handle the submission. However, whenever someone clicks on Submit, the page redirects to display my PHP code instead of a ...

In the iOS app when the Ionic HTML5 select keypad is opened, a bug causes the view to scroll upwards and create empty space after tapping the tab

I am currently working with Ionic v1 and AngularJS, utilizing ion tabs: <ion-tabs class="tabs-icon-top tabs-color-active-positive"> <!-- Home Tab --> <ion-tab icon-off="ion-home" icon-on="ion-home" href="#/tab/home"> <ion-nav ...

Moving the layout container towards the left: a quick guide

I am currently attempting to display the legend contents in a horizontal alignment within the layout container. The issue is that while the layout containing the legend aligns horizontally as desired, it extends beyond the screen border. I do not want the ...

Uniform Fixed Width for Material UI Table Cells

Below is a Material UI Table with columns set to a fixed size of 205px each, and the table itself set to 100% width. However, when there isn't enough space available, the columns do not shrink according to the text inside them, remaining stuck at 205p ...

Laravel triggers a 'required' error message when all fields have been filled

As I attempt to submit a form using an axios post request in laravel, I encounter an issue with the validation of the name and age fields, along with an image file upload. Here is a breakdown of the form structure: Below is the form setup: <form actio ...

Enhance your website with a dynamic 'click to update' feature using ajax

I'm in the process of developing a straightforward list application where users can easily edit items by clicking on them. Although everything seems to be working fine, I'm encountering an issue with saving changes to the database. For some reaso ...

Utilize localStorage.getItem() in conjunction with TypeScript to retrieve stored data

Within my codebase, I have the following line: const allGarments = teeMeasuresAverages || JSON.parse(localStorage.getItem("teeMeasuresAverages")) || teeMeasuresAveragesLocal; Unexpectedly, Typescript triggers an alert with this message: Argument ...

Utilizing useEffect Hooks to Filter Local JSON Data in React Applications

For my engineering page, I have been developing a user display that allows data to be filtered by field and expertise. Fields can be filtered through a dropdown menu selection, while expertise can be filtered using an input field. My initial plan was to ...

Error caused by live data dynamic update on Highstock chart: Cannot access property 'info' of undefined

Utilizing Laravel, I was able to create Highcharts by consuming data from a Rest API link (Spring app) at . The data is currently displayed statically, but I want to dynamically visualize it on charts. I attempted to follow this example on Fiddle and inte ...

Rails confirmation feature malfunctioning

I have been struggling to figure out where I am going wrong with this code even though I've checked several posts. I am using Ruby on Rails and trying to run the following snippet: <%= link_to 'Destroy', article_path(article), ...

Making @types compatible with TypeScript 2.1 in Visual Studio 2015 Update 3

The potential of TS 2.x @types is intriguing, but I'm struggling to make it work effectively! I'm using Visual Studio 2015 - version 14.0.25431.01 Update 3 My TypeScript version for Visual Studio 2015 is 2.1.4, downloaded from this link The VS ...

Retrieve all documents from a Firebase User's collection

Recently, I created a Firebase cloud function where my goal is to access every user's internal collection named 'numbers' and examine each document within that collection for some comparisons. Do you have any insights on how I can achieve t ...

Assign the state to a new object by preserving matching values from the previous state

In my current state, I have an object structured like this: stateObject = {0: 400, 1: 500, 2: 600} Whenever my component rerenders on componentWillUpdate, an additional column is added carrying over the value at key index 0 (400). My goal is to update th ...

Calculate the sum of the products when multiplying two values from every array of objects, using Reactjs/Javascript

I'm currently developing an eCommerce application and need to calculate the total price of items that users have ordered. I have an array named 'orders' which contains all the ordered items, each item has two keys - payablePrice and purchase ...