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.tsexport 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.tsimport { 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.tsimport * 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