Home > OS >  Typescript: define type with a getter and setter
Typescript: define type with a getter and setter

Time:11-28

I have a decorator that define property a class with a getter that return an Observable and setter that emit new value on subject.

import { Subject } from "rxjs";

export const defineDecorator = () => {
  return (target: object, propertyKey: string) => {
    Object.defineProperty(target, propertyKey, {
      const sub = new Subject();
      const obs = sub.asObservable();
      get: (): any => {
        return obs;
      },
      set: (value: any) => {
        sub.next(value);
      },
      enumerable: true,
      configurable: true,
    });
  }
};

I want specify in typescript, "value" propery is Observable<string> on "get" and string on "set":

export class MyComponent {
  @defineDecorator()
  value!: any; // instead of any.. what type?
}

what type for "value"?

CodePudding user response:

This pattern is not currently supported in TypeScript (as of version 4.9.3 at least). Here's a simplified example which demonstrates the issue:

TS Playground

interface Foo {
  get bar (): { value: string }; /*
      ~~~
  The return type of a 'get' accessor must be assignable to its 'set' accessor type(2380) */
  set bar (value: string);
}

Similar to the example above — in the code you've shown, the return type of the getter would be Observable<string>, which is not assignable to the setter's parameter type string.


You can read more context about this pattern and behavior at the following GitHub links:

CodePudding user response:

Per the previous answer, it's not possible but you can do it differently. What if instead of plain property you return a function that will have overload signature and depending on how you can it will act as a setter or getter

export const defineDecorator = () => (target: object, propertyKey: string) => {
  const sub = new Subject();
  const obs = sub.asObservable();

  Object.defineProperty(target, propertyKey, {
    enumerable: true,
    configurable: true,
    value: (param: any) => {
      if (param !== undefined) {
        sub.next(param);
      } else {
        return obs;
      }
    },
  });
};

interface Value<T> {
  (): Observable<T>;
  (s: T): void;
}

export class MyComponent {
  @defineDecorator()
  value!: Value<string>;

  constructor() {
    // if used without param returns observable
    const shouldBeObservable = this.value();

    // if param is passed used as setter
    this.value('some string here');
  }
}

  • Related