javascripttypescriptclass-decorator

Implementing JS decorator to wrap class


I'm trying to wrap class constructor and inject to some logic by using class decorator. Everything worked fine until I have tried to extend wrapped class: Extended class don't have methods in prototype.

    function logClass(Class) {
      // save a reference to the original constructor
      const _class = Class;
    
      // proxy constructor
      const proxy = function(...args) {
        const obj = new _class(...args);
        // ... add logic here
        return obj
      }
    
      // copy prototype so intanceof operator still works
      proxy.prototype = _class.prototype;
    
      // return proxy constructor (will override original)
      return proxy;
    }
    
    @logClass
    class Base {
      prop = 5;
      test() {
        console.log("test")
      }
    }
    
    class Extended extends Base {
      test2() {
        console.log("test2")
      }
    }
    
    var base = new Base()
    base.test()
    var ext = new Extended()
    console.log(ext.prop)
    ext.test()
    ext.test2() // TypeError: ext.test2 is not a function

Solution

  • Okay so I tried to figure out what is "wrong" with your code, but I was not able to make it work because it didn't typecheck. So, as a last resort, I'm posting a partial answer of my attempt, which works (with some quirks) so I can help other users who are more savvy with TypeScript.

    First of all, the quirks: class decorators in TS cannot modify the structure of a type, so if you wanted to, for example, add a method to the decorated class, you would be able to do it but you would have to eat up/suppress unavoidable type errors (TS2339) when calling those methods.

    There is a work around for this in this other question: Typescript adding methods with decorator type does not exist, but you would lose this current clean syntax for decorators if you do this.

    Now, my solution, taken more or less directly from the documentation:

    function logClass<T extends { new(...args: any[]): {} }>(constructor: T) {
      return class extends constructor {
        constructor(...args: any[]) {
          super(args);
          // ...add programmatic logic here
          //    (`super` is the decorated class, of type `T`, here)
        }
    
        // ...add properties and methods here
        log(message: string) {        // EXAMPLE
          console.log(`${super.constructor.name} says: ${message}`);
        }
      }
    }
        
    @logClass
    class Base {
      prop = 5;
      test() {
        console.log("test");
      }
    
      constructor() {}
    }
    
    class Extended extends Base {
      test2() {
        console.log("test2");
      }
    }
    
    var base = new Base();
    base.test();
    var ext = new Extended();
    console.log(ext.prop);
    //base.log("Hello");  // unavoidable type error TS2339
    ext.test();
    ext.test2();