[Typescript] Decorator - PropertyDecorator, ClassDecorator, MethodDecorator

MethodDecorator

  • @Log(level): Console log message
  • @Pref: Mesure time
function Log(level: LoggingLevel): MethodDecorator {
  return (
    target: any,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor
  ) => {
    console.log("target", target);
    console.log("propertyKey", propertyKey);
    console.log("descriptor", descriptor);

    const originalFn = descriptor.value;
    // Cannot use arrow function
    descriptor.value = function (...args: any[]) {
      if (level <= appMaxLoggingLevel) {
        console.log(
          `>> Log: ${propertyKey.toString()}, ${JSON.stringify(args)}`
        );
      }
      originalFn.apply(this, args);
    };
  };
}

function Pref() {
  return (
    target: any,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor
  ) => {
    const originalFn = descriptor.value;
    descriptor.value = function (...args: any[]) {
      console.log(`started at ${new Date().getTime()}`);
      originalFn.apply(this, args);
      console.log(`ended at ${new Date().getTime()}`);
    };
  };
}

 

Usage:

enum LoggingLevel {
  ERROR,
  INFO,
  WARN,
  DEBUG,
  TRACE,
}

const appMaxLoggingLevel = LoggingLevel.WARN;

class DbService {
  // saveData function is wrapped in Log
  // Pref wrap both
  @Pref()
  @Log(LoggingLevel.INFO)
  saveData(data: any) {
    console.log(`saving data in the database...`);
  }
}

const db = new DbService();
db.saveData({ hello: "world" }); // >> Log: saveData, [{"hello":"world"}]

/*
target {}
propertyKey saveData
descriptor {
  value: [Function: saveData],
  writable: true,
  enumerable: false,
  configurable: true
}
started at 1668938096069
>> Log: saveData, [{"hello":"world"}]
saving data in the database...
ended at 1668938096069
*/

 

 ClassDecorator

  • SealClass: Cannot modify class & it's prototype
// @SealClass()
function SealClass(): ClassDecorator {
  return (constructor: Function) => {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
  };
}

// @SealClass
// function SealClass(constructor: Function) {
//   Object.seal(constructor);
//   Object.seal(constructor.prototype);
// }

 

Usage:

@SealClass()
class DbService {
  // saveData function is wrapped in Log
  // Pref wrap both
  @Pref()
  @Log(LoggingLevel.INFO)
  saveData(data: any) {
    console.log(`saving data in the database...`);
  }
}

const db = new DbService();
// TypeError: Cannot define property sayHello, object is not extensible
Object.defineProperty(DbService, "sayHello", {
  value: () => {
    console.log("Should error");
  },
});

 

PropertyDecorator

  • @DatabaseId: auto generate id if not yet defined, otherwise return generated id 
function DatabaseId(): PropertyDecorator {
  return (classPrototype: any, propertyKey: string | symbol) => {
    Object.defineProperty(classPrototype, propertyKey, {
      get: function () {
        if (!this["_id"]) {
          this["_id"] =
            Date.now().toString(36) + Math.random().toString(36).slice(2);
        }
        return this["_id"];
      },
    });
  };
}

 

Usage:

class Course {
  @DatabaseId()
  id: string;

  title: string;

  constructor(title: string) {
    this.title = title;
  }

  print(message: string) {
    console.log(`${message}, Course ${this.title}, id ${this.id}`);
  }
}

const course1 = new Course("Typescript");
// Course 1 id:  lap621lv3gohkrz1ghf
console.log(`Course 1 id: `, course1.id);
// Course 1:  Course { title: 'Typescript', _id: 'lap621lv3gohkrz1ghf' }
console.log(`Course 1: `, course1);
const course2 = new Course("Angular");
// Course 2 id:  lap621lwisnfp23ubo
console.log(`Course 2 id: `, course2.id);
// Course 2:  Course { title: 'Angular', _id: 'lap621lwisnfp23ubo' }
console.log(`Course 2: `, course2);

 

 

----Full Code---

enum LoggingLevel {
  ERROR,
  INFO,
  WARN,
  DEBUG,
  TRACE,
}

function Log(level: LoggingLevel): MethodDecorator {
  return (
    target: any,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor
  ) => {
    console.log("target", target);
    console.log("propertyKey", propertyKey);
    console.log("descriptor", descriptor);

    const originalFn = descriptor.value;
    // Cannot use arrow function
    descriptor.value = function (...args: any[]) {
      if (level <= appMaxLoggingLevel) {
        console.log(
          `>> Log: ${propertyKey.toString()}, ${JSON.stringify(args)}`
        );
      }
      originalFn.apply(this, args);
    };
  };
}

function Pref() {
  return (
    target: any,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor
  ) => {
    const originalFn = descriptor.value;
    descriptor.value = function (...args: any[]) {
      console.log(`started at ${new Date().getTime()}`);
      originalFn.apply(this, args);
      console.log(`ended at ${new Date().getTime()}`);
    };
  };
}

// @SealClass()
function SealClass(): ClassDecorator {
  return (constructor: Function) => {
    Object.seal(constructor);
    Object.seal(constructor.prototype);
  };
}

// @SealClass
// function SealClass(constructor: Function) {
//   Object.seal(constructor);
//   Object.seal(constructor.prototype);
// }

function DatabaseId(): PropertyDecorator {
  return (classPrototype: any, propertyKey: string | symbol) => {
    Object.defineProperty(classPrototype, propertyKey, {
      get: function () {
        if (!this["_id"]) {
          this["_id"] =
            Date.now().toString(36) + Math.random().toString(36).slice(2);
        }
        return this["_id"];
      },
    });
  };
}

const appMaxLoggingLevel = LoggingLevel.WARN;

@SealClass()
class DbService {
  // saveData function is wrapped in Log
  // Pref wrap both
  @Pref()
  @Log(LoggingLevel.INFO)
  saveData(data: any) {
    console.log(`saving data in the database...`);
  }
}

class Course {
  @DatabaseId()
  id: string;

  title: string;

  constructor(title: string) {
    this.title = title;
  }

  print(message: string) {
    console.log(`${message}, Course ${this.title}, id ${this.id}`);
  }
}

const db = new DbService();
db.saveData({ hello: "world" }); // >> Log: saveData, [{"hello":"world"}]
/*
// TypeError: Cannot define property sayHello, object is not extensible
Object.defineProperty(DbService, "sayHello", {
  value: () => {
    console.log("Should error");
  },
});
*/
/////

const course1 = new Course("Typescript");
// Course 1 id:  lap621lv3gohkrz1ghf
console.log(`Course 1 id: `, course1.id);
// Course 1:  Course { title: 'Typescript', _id: 'lap621lv3gohkrz1ghf' }
console.log(`Course 1: `, course1);
const course2 = new Course("Angular");
// Course 2 id:  lap621lwisnfp23ubo
console.log(`Course 2 id: `, course2.id);
// Course 2:  Course { title: 'Angular', _id: 'lap621lwisnfp23ubo' }
console.log(`Course 2: `, course2);

 

posted @ 2022-11-20 18:01  Zhentiw  阅读(65)  评论(0编辑  收藏  举报