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?
I was talking about class decorators in typescript in my previous post, today is time for properties decorators, how to define and use it for writing clean and elegant code. The property decorator is a function, applied to the property declaration in our classes.
NOTE TypeScript disallows decorating both the get and set accessor for a single member. Instead, all decorators for the member must be applied to the first accessor specified in document order. This is because decorators apply to a Property Descriptor, which combines both the get and set accessor, not each declaration separately.
Decorators are JavaScript functions. Decorators can also have parameters by returning an inner function. When a Property Decorator is executed at runtime, the prototype is passed as a target reference. A second parameter is passed as the name of the property that the Decorator is placed on.
TypeScript includes experimental support for emitting certain types of metadata for declarations that have decorators. To enable this experimental support, you must set the emitDecoratorMetadata compiler option either on the command line or in your tsconfig.json:
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.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With