Reputation: 852
I'm trying to understand TypeScript decorators (specifically for properties), and I came up with the following code based on some examples I've seen:
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 problem is that when I try to run this, I get:
TypeError: Cannot read property 'firstName' of undefined
It seems that the value of this
is undefined. What am I missing?
For reference, my tsconfig.json
has:
"target": "es5"
"experimentalDecorators": true
"strict": false
UPDATE 8/27
It seems that this issue only arises when the decorator is in a different .ts
file. If you place the decorator in a different file and import it from another file, then the error occurs. However, placing them all in the same file doesn't cause the issue. Am I simply misunderstanding how this
gets interpreted?
Upvotes: 10
Views: 6314
Reputation: 1
I met the similar issue but w/ a serial strict conditions.
My code was like below:
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 was using typescript 3.7.0+ w/ a lower ts-node version, all goes fine no matter running via ts-node or via webpack && node xxx.
Later I upgrade to typescript 4.7.0+ w/ a ts-node 10.0.0+ version, the webpack & node run goes fine as well, however ts-node | ts-node-dev give a runtime issue:
/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)
It major said StringUtil
is undefined when using StringUtil.validate
as I could see.
I doubt the reason might like @CJ Harries mentioned above but I'm not sure, playing w/ typescript it looks namespace would be compiled as an object so it's wired... Maybe the upgrade caused any break change when on decorator while via ts-node?
Upvotes: 0
Reputation: 171
I used allen wang post to build from and came up with this
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
});
}
}
Upvotes: 0
Reputation: 1147
you can try this:
instead of
function logProperty(target: any, key: string) {
...
}
using:
const logProperty = (target: any, key: string) => {
...
}
because =>
's this
just outside, so it can get it.
hope it's helpful!
Upvotes: 0
Reputation: 481
'this' is undefined because you are not properly configuring the property descriptor.
Instead of:
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
Do something like:
Object.defineProperty(target, key, {
get() {
// this is defined
},
set(value: any) {
// this is defined
},
enumerable: true,
configurable: true
});
I had the same issue and the solution above solves it.
Upvotes: 0
Reputation: 178
tl;dr: I'm not sure why OP's config didn't work; it seems to work beautifully now. See below for some brute-force testing.
I'm wondering if you were somehow picking up the wrong tsconfig
. I've looked at your repo's tsconfig
and it looks correct. Is there any chance another config file was infecting those runs? I see there were no automated tests there.
I ran into a similar issue today and threw together a quick test using OP as a blueprint. I pulled compiler options from the official docs.
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
import * as assert from "assert";
import * as shelljs from "shelljs";
const MODULE_GENERATION = [
"CommonJS",
"AMD",
"System",
"UMD",
"ES6",
"ES2015",
"ESNext",
];
const TARGETS = [
"ES5",
"ES2015",
"ES2016",
"ES2017"
]
shelljs.exec("tsc --target 'ES5' --module 'None' --strict main.ts", { silent: true });
assert.ok(shelljs.error());
shelljs.exec("tsc --target 'ES5' --module 'None' main.ts", { silent: true });
assert.ok(shelljs.error());
for (const moduleGeneration of MODULE_GENERATION) {
console.log(`Testing module generation: ${moduleGeneration}`);
for (const target of TARGETS) {
console.log(` Building for ${target}`);
for (const strict of [true, false]) {
console.log(` Strict mode: ${strict ? 'en' : 'dis'}abled`)
const command = (
`tsc` +
` --module '${moduleGeneration}'` +
` --experimentalDecorators` +
` --target '${target}'` +
` ${strict ? "--strict" : ""}` +
` main.ts`
);
const output = shelljs.exec(
command,
{ silent: true },
);
let symbol;
if (strict) {
assert.ok(shelljs.error());
symbol = '✖'
} else {
assert.strictEqual(0, output.code);
symbol = '✓'
}
console.log(` ${symbol} ${command}`);
}
}
}
You can see the full build on Travis.
Testing module generation: CommonJS
Building for ES5
Strict mode: enabled
✖ tsc --module 'CommonJS' --experimentalDecorators --target 'ES5' --strict main.ts
Strict mode: disabled
✓ tsc --module 'CommonJS' --experimentalDecorators --target 'ES5' main.ts
Building for ES2015
Strict mode: enabled
✖ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2015' --strict main.ts
Strict mode: disabled
✓ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2015' main.ts
Building for ES2016
Strict mode: enabled
✖ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2016' --strict main.ts
Strict mode: disabled
✓ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2016' main.ts
Building for ES2017
Strict mode: enabled
✖ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2017' --strict main.ts
Strict mode: disabled
✓ tsc --module 'CommonJS' --experimentalDecorators --target 'ES2017' main.ts
Testing module generation: AMD
Building for ES5
Strict mode: enabled
✖ tsc --module 'AMD' --experimentalDecorators --target 'ES5' --strict main.ts
Strict mode: disabled
✓ tsc --module 'AMD' --experimentalDecorators --target 'ES5' main.ts
Building for ES2015
Strict mode: enabled
✖ tsc --module 'AMD' --experimentalDecorators --target 'ES2015' --strict main.ts
Strict mode: disabled
✓ tsc --module 'AMD' --experimentalDecorators --target 'ES2015' main.ts
Building for ES2016
Strict mode: enabled
✖ tsc --module 'AMD' --experimentalDecorators --target 'ES2016' --strict main.ts
Strict mode: disabled
✓ tsc --module 'AMD' --experimentalDecorators --target 'ES2016' main.ts
Building for ES2017
Strict mode: enabled
✖ tsc --module 'AMD' --experimentalDecorators --target 'ES2017' --strict main.ts
Strict mode: disabled
✓ tsc --module 'AMD' --experimentalDecorators --target 'ES2017' main.ts
Testing module generation: System
Building for ES5
Strict mode: enabled
✖ tsc --module 'System' --experimentalDecorators --target 'ES5' --strict main.ts
Strict mode: disabled
✓ tsc --module 'System' --experimentalDecorators --target 'ES5' main.ts
Building for ES2015
Strict mode: enabled
✖ tsc --module 'System' --experimentalDecorators --target 'ES2015' --strict main.ts
Strict mode: disabled
✓ tsc --module 'System' --experimentalDecorators --target 'ES2015' main.ts
Building for ES2016
Strict mode: enabled
✖ tsc --module 'System' --experimentalDecorators --target 'ES2016' --strict main.ts
Strict mode: disabled
✓ tsc --module 'System' --experimentalDecorators --target 'ES2016' main.ts
Building for ES2017
Strict mode: enabled
✖ tsc --module 'System' --experimentalDecorators --target 'ES2017' --strict main.ts
Strict mode: disabled
✓ tsc --module 'System' --experimentalDecorators --target 'ES2017' main.ts
Testing module generation: UMD
Building for ES5
Strict mode: enabled
✖ tsc --module 'UMD' --experimentalDecorators --target 'ES5' --strict main.ts
Strict mode: disabled
✓ tsc --module 'UMD' --experimentalDecorators --target 'ES5' main.ts
Building for ES2015
Strict mode: enabled
✖ tsc --module 'UMD' --experimentalDecorators --target 'ES2015' --strict main.ts
Strict mode: disabled
✓ tsc --module 'UMD' --experimentalDecorators --target 'ES2015' main.ts
Building for ES2016
Strict mode: enabled
✖ tsc --module 'UMD' --experimentalDecorators --target 'ES2016' --strict main.ts
Strict mode: disabled
✓ tsc --module 'UMD' --experimentalDecorators --target 'ES2016' main.ts
Building for ES2017
Strict mode: enabled
✖ tsc --module 'UMD' --experimentalDecorators --target 'ES2017' --strict main.ts
Strict mode: disabled
✓ tsc --module 'UMD' --experimentalDecorators --target 'ES2017' main.ts
Testing module generation: ES6
Building for ES5
Strict mode: enabled
✖ tsc --module 'ES6' --experimentalDecorators --target 'ES5' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ES6' --experimentalDecorators --target 'ES5' main.ts
Building for ES2015
Strict mode: enabled
✖ tsc --module 'ES6' --experimentalDecorators --target 'ES2015' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ES6' --experimentalDecorators --target 'ES2015' main.ts
Building for ES2016
Strict mode: enabled
✖ tsc --module 'ES6' --experimentalDecorators --target 'ES2016' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ES6' --experimentalDecorators --target 'ES2016' main.ts
Building for ES2017
Strict mode: enabled
✖ tsc --module 'ES6' --experimentalDecorators --target 'ES2017' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ES6' --experimentalDecorators --target 'ES2017' main.ts
Testing module generation: ES2015
Building for ES5
Strict mode: enabled
✖ tsc --module 'ES2015' --experimentalDecorators --target 'ES5' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ES2015' --experimentalDecorators --target 'ES5' main.ts
Building for ES2015
Strict mode: enabled
✖ tsc --module 'ES2015' --experimentalDecorators --target 'ES2015' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ES2015' --experimentalDecorators --target 'ES2015' main.ts
Building for ES2016
Strict mode: enabled
✖ tsc --module 'ES2015' --experimentalDecorators --target 'ES2016' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ES2015' --experimentalDecorators --target 'ES2016' main.ts
Building for ES2017
Strict mode: enabled
✖ tsc --module 'ES2015' --experimentalDecorators --target 'ES2017' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ES2015' --experimentalDecorators --target 'ES2017' main.ts
Testing module generation: ESNext
Building for ES5
Strict mode: enabled
✖ tsc --module 'ESNext' --experimentalDecorators --target 'ES5' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ESNext' --experimentalDecorators --target 'ES5' main.ts
Building for ES2015
Strict mode: enabled
✖ tsc --module 'ESNext' --experimentalDecorators --target 'ES2015' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ESNext' --experimentalDecorators --target 'ES2015' main.ts
Building for ES2016
Strict mode: enabled
✖ tsc --module 'ESNext' --experimentalDecorators --target 'ES2016' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ESNext' --experimentalDecorators --target 'ES2016' main.ts
Building for ES2017
Strict mode: enabled
✖ tsc --module 'ESNext' --experimentalDecorators --target 'ES2017' --strict main.ts
Strict mode: disabled
✓ tsc --module 'ESNext' --experimentalDecorators --target 'ES2017' main.ts
tsconfig
Based on those results, it looks like this is an okay tsconfig
.
{
"compilerOptions": {
"target": "es5",
"module": "<not None>",
"experimentalDecorators": true,
"strict": false
}
}
2.4.2
and I'm using 2.7.2
. Just to make sure that wasn't the issue, I bumped my version down too.Upvotes: 1