Michael Michailidis
Michael Michailidis

Reputation: 1052

Typescript superclass identifying from a decorator

I have an abstract class like the following

export abstract class Foo {
  public f1() {
  }
}

and two more classes extending the base

export class Boo extends Foo {
}

export class Moo extends Foo {
}

Now I have a custom decorator like the following

export function Bla() {
  return (target: any, key: string, descriptor: PropertyDescriptor) => {
  }
}

so my initial class is the following (with the decorator)

export abstract class Foo {
 @Bla
 public f1() {
 }
}

is there a way in the decorator to distinguish which call sourced from each superclass?

so far what I have tried are checking prototypes / constructors of target but I do not seem to find a way to access / understand from which class it was sourced. Is there a way to figure it out or I am doing something really wrong?

Thank you.

Upvotes: 1

Views: 705

Answers (2)

John Weisz
John Weisz

Reputation: 31924

The trick to it is tapping into the method call itself and inspecting the class instance:

function Bla(target: any, propKey: string | symbol | d: PropertyDescriptor) {
  let originalMethod = target[propKey];

  // return new property descriptor for the method, replacing the original one
  return {
    value: function () {
      let instance = this; // will be the instance ref because of 'function' literal
      let classReference = instance.constructor; // <-- this is what we need

      if (classReference === Boo) {
        // called from Boo class
      }

      // call original method
      return originalMethod.apply(this, arguments);
    }
  }
}

Upvotes: 2

T.J. Crowder
T.J. Crowder

Reputation: 1074335

Because you're decorating a prototype method, the decorator is applied when class construct the decorator is in is evaluated, not later when instances are created. It's only applied to the prototype member for that class (subclasses only get the decorated member via inheritance).

Assuming you have:

function Bla() {
  return (target: any, key: string, descriptor: PropertyDescriptor) => {
    console.log(target.constructor.name);
  }
}

abstract class Foo {
  @Bla()
  public f1() {
  }
}

// At this point, you see "Foo" in the console

class Boo extends Foo {
}

class Moo extends Foo {
}

The decorator will run when class Foo is evaluated, not later when you create instances. You can see this happen in the playground. If you have this code after the class definitions above:

setTimeout(() => {
    new Boo; // Nothing shows in the console
    setTimeout(() => {
        new Moo; // Nothing shows in the console
        console.log("Done");
    }, 1000);
}, 1000);

If you were decorating an instance member, you'd be able to differentiate because the instance would be a Boo or Moo, but not when you're decorating a prototype member.

Upvotes: 1

Related Questions