Skip to content
This repository has been archived by the owner on Oct 29, 2024. It is now read-only.

Tracked descriptors not accessible after property initialization #415

Open
dill-larson opened this issue Sep 11, 2024 · 3 comments
Open

Comments

@dill-larson
Copy link

Background

I'm attempting to create a custom decorator to apply to a @tracked property in order to execute a function whenever the property's value is set.

Here's a simplified version of the component and the decorator:

// component.ts
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { log } from './decorators';
export default class Foo extends Component {
  @tracked
  @log
  bar = '';
}
// decorators.ts
export default function log(target: Object, propertyKey: string): ReturnType<PropertyDecorator> {
  const trackedDescriptor = Object.getOwnPropertyDescriptor(target, propertyKey); // returns undefined

  Object.defineProperty(target, propertyKey, {
    enumerable: true,
    writable: true,
    get() {
        const value = trackedDescriptor.get?.call(this);
        return value;
    },
    set(newValue: unknown) {
        trackedDescriptor.set?.call(this, newValue);
        // custom logic
        console.log(newValue);
    }
  });
}

Problem

When trying to access the tracked descriptor in a subsequent decorator, it will always be undefined. Thus preventing me from creating a custom decorator for a @tracked property. Am I going about this incorrectly? Any help would be appreciated, thanks!

Version

ember-cli: 3.28.6
node: 16.20.0
os: darwin arm64
@glimmer/component: "^1.0.4"
@glimmer/tracking: "^1.0.4"
@NullVoxPopuli
Copy link
Contributor

Would this work for your usecase?

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { on } from '@ember/modifier';

/**
 * Note that when an app switches to accessor decorators, 
 * this won't work
 */
function log(target, propertyKey, descriptor) {
  let {get, set} = descriptor;
  Object.assign(descriptor, {
    get() {
      console.log('get', propertyKey);
      return get.call(this);
    },
    set(newValue) {
      console.log('set', propertyKey, newValue);
      return set.call(this, newValue);
    }
  });
}


export default class extends Component {
  @log
  @tracked 
  foo = '';

  change = () => this.foo += Math.random().toString().split('').at(-1);

  <template>
    foo: {{this.foo}}
    <br>
    <button {{on 'click' this.change}}>change foo</button>
  </template>
}

@dill-larson
Copy link
Author

Would this work for your usecase?

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { on } from '@ember/modifier';

/**
 * Note that when an app switches to accessor decorators, 
 * this won't work
 */
function log(target, propertyKey, descriptor) {
  let {get, set} = descriptor;
  Object.assign(descriptor, {
    get() {
      console.log('get', propertyKey);
      return get.call(this);
    },
    set(newValue) {
      console.log('set', propertyKey, newValue);
      return set.call(this, newValue);
    }
  });
}


export default class extends Component {
  @log
  @tracked 
  foo = '';

  change = () => this.foo += Math.random().toString().split('').at(-1);

  <template>
    foo: {{this.foo}}
    <br>
    <button {{on 'click' this.change}}>change foo</button>
  </template>
}

So technically yes this works, however, the TypeScript compiler is going to error with
Unable to resolve signature of property decorator when called as an expression. The runtime will invoke the decorator with 2 arguments, but the decorator expects 3 since it is expecting log to be PropertyDecorator

@NullVoxPopuli
Copy link
Contributor

oh yes, the TS types for decorators are way wrong for what we do, so when you type a decorator you need all the lies.

This isn't fixed until we move to supporting the spec-decorators coming out of the TC39 process

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants