Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'this' is Undefined in TypeScript Property Decorator

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?

like image 791
Andrew M. Avatar asked Aug 23 '17 04:08

Andrew M.


People also ask

What is a properties decorator in typescript?

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.

Why can’t I decorate the get and set accessor in typescript?

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.

What are decorators in JavaScript?

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.

How do I enable typescript to emit metadata for declarations with decorators?

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:


Video Answer


1 Answers

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.

Guess

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.

Testing

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}`);
        }
    }
}

Results

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

Solid tsconfig

Based on those results, it looks like this is an okay tsconfig.

{
  "compilerOptions": {
    "target": "es5",
    "module": "<not None>",
    "experimentalDecorators": true,
    "strict": false
  }
}

Final Notes

  • I didn't test as many compiler options as I could have. If there's interest, I can update the repo later.
  • I still don't have a good answer for OP's issue and that bothers me.
  • I did check TS version as well. OP was using 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.
like image 55
CJ Harries Avatar answered Oct 04 '22 09:10

CJ Harries