├── .gitignore ├── README.md ├── img ├── decorator-evalution-execution.png ├── decorator-sample.png └── signatures.png ├── index.html ├── package.json ├── src ├── class-decorator.ts ├── decorators.ts ├── gist │ ├── gist-accessor-decorator.js │ ├── gist-accessor-decorator.ts │ ├── gist-class-decorator.js │ ├── gist-class-decorator.ts │ ├── gist-decorator.ts │ ├── gist-method-decorator.js │ ├── gist-method-decorator.ts │ ├── gist-parameter-decorator.js │ ├── gist-parameter-decorator.ts │ ├── gist-property-decorator.js │ ├── gist-property-decorator.ts │ ├── metadata-reflection-api.js │ └── metadata-reflection-api.ts ├── index.ts ├── method-decorator.ts ├── parameter-decorator.ts └── property-decorator.ts ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # production 5 | dist 6 | 7 | # misc 8 | npm-debug.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeScript decorator # 2 | This project helps to understand decorators and different kinds of decorators and its samples. 3 | 4 | To run this project 5 | 1. Clone this git repository 6 | 2. run npm start 7 | 3. host the root directory via ```http-server``` [if you dont have http-server. Run this command ```npm i http-server --g```] -------------------------------------------------------------------------------- /img/decorator-evalution-execution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohanramphp/typescript-decorators/0a13b2a15e311fb6fef5fa9d3060329c14e5562f/img/decorator-evalution-execution.png -------------------------------------------------------------------------------- /img/decorator-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohanramphp/typescript-decorators/0a13b2a15e311fb6fef5fa9d3060329c14e5562f/img/decorator-sample.png -------------------------------------------------------------------------------- /img/signatures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohanramphp/typescript-decorators/0a13b2a15e311fb6fef5fa9d3060329c14e5562f/img/signatures.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TypeScript Decorators 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-decorator", 3 | "version": "0.0.1", 4 | "description": "", 5 | "scripts": { 6 | "prestart": "yarn", 7 | "start": "webpack --watch", 8 | "dev-server": "webpack-dev-server --hotOnly" 9 | }, 10 | "keywords": [], 11 | "author": "Mohan Ram", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@webpack-cli/serve": "^0.1.2", 15 | "ts-loader": "^5.3.1", 16 | "typescript": "^3.2.2", 17 | "webpack": "^4.20.2", 18 | "webpack-cli": "^3.1.2", 19 | "webpack-dev-server": "^3.1.10" 20 | }, 21 | "dependencies": { 22 | "reflect-metadata": "^0.1.12" 23 | } 24 | } -------------------------------------------------------------------------------- /src/class-decorator.ts: -------------------------------------------------------------------------------- 1 | export function logClass(target: Function) { 2 | // save a reference to the original constructor 3 | const original = target; 4 | 5 | // a utility function to generate instances of a class 6 | function construct(constructor, args) { 7 | const c: any = function () { 8 | return constructor.apply(this, args); 9 | } 10 | c.prototype = constructor.prototype; 11 | return new c(); 12 | } 13 | 14 | // the new constructor behaviour 15 | const f: any = function (...args) { 16 | console.log("New: " + original['name']); 17 | return construct(original, args); 18 | } 19 | 20 | // copy prototype so intanceof operator still works 21 | f.prototype = original.prototype; 22 | 23 | // return new constructor (will override original) 24 | return f; 25 | } -------------------------------------------------------------------------------- /src/decorators.ts: -------------------------------------------------------------------------------- 1 | import { logClass } from './class-decorator'; 2 | import { logMethod } from './method-decorator'; 3 | import { logProperty } from './property-decorator'; 4 | import { logParameter } from './parameter-decorator'; 5 | 6 | export function log(...args) { 7 | switch (args.length) { 8 | case 3: 9 | if (typeof args[2] === "number") { 10 | return logParameter.apply(this, args); 11 | } 12 | return logMethod.apply(this, args); 13 | case 2: 14 | return logProperty.apply(this, args); 15 | case 1: 16 | return logClass.apply(this, args); 17 | default: 18 | throw new Error('Not a valid decorator'); 19 | } 20 | } -------------------------------------------------------------------------------- /src/gist/gist-accessor-decorator.js: -------------------------------------------------------------------------------- 1 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 2 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 3 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 4 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 5 | return c > 3 && r && Object.defineProperty(target, key, r), r; 6 | }; 7 | function enumerable(value) { 8 | return function (target, propertyKey, descriptor) { 9 | console.log('decorator - sets the enumeration part of the accessor'); 10 | descriptor.enumerable = value; 11 | }; 12 | } 13 | var Employee = /** @class */ (function () { 14 | function Employee() { 15 | } 16 | Object.defineProperty(Employee.prototype, "salary", { 17 | get: function () { return "Rs. " + this._salary; }, 18 | set: function (salary) { this._salary = +salary; }, 19 | enumerable: true, 20 | configurable: true 21 | }); 22 | Object.defineProperty(Employee.prototype, "name", { 23 | get: function () { 24 | return "Sir/Madam, " + this._name; 25 | }, 26 | set: function (name) { 27 | this._name = name; 28 | }, 29 | enumerable: true, 30 | configurable: true 31 | }); 32 | __decorate([ 33 | enumerable(false) 34 | ], Employee.prototype, "salary", null); 35 | __decorate([ 36 | enumerable(true) 37 | ], Employee.prototype, "name", null); 38 | return Employee; 39 | }()); 40 | var emp = new Employee(); 41 | emp.salary = 1000; 42 | for (var prop in emp) { 43 | console.log("enumerable property = " + prop); 44 | } 45 | // salary property - will not be part of enumeration since we configured it to false 46 | // output: 47 | // enumerable property = _salary 48 | -------------------------------------------------------------------------------- /src/gist/gist-accessor-decorator.ts: -------------------------------------------------------------------------------- 1 | 2 | function enumerable(value: boolean) { 3 | return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { 4 | console.log('decorator - sets the enumeration part of the accessor'); 5 | descriptor.enumerable = value; 6 | }; 7 | } 8 | 9 | class Employee { 10 | private _salary: number; 11 | private _name: string; 12 | 13 | @enumerable(false) 14 | get salary() { return `Rs. ${this._salary}`; } 15 | 16 | set salary(salary: any) { this._salary = +salary; } 17 | 18 | @enumerable(true) 19 | get name() { 20 | return `Sir/Madam, ${this._name}`; 21 | } 22 | 23 | set name(name: string) { 24 | this._name = name; 25 | } 26 | 27 | } 28 | 29 | const emp = new Employee(); 30 | emp.salary = 1000; 31 | for (let prop in emp) { 32 | console.log(`enumerable property = ${prop}`); 33 | } 34 | // salary property - will not be part of enumeration since we configured it to false 35 | // output: 36 | // decorator - sets the enumeration part of the accessor 37 | // decorator - sets the enumeration part of the accessor 38 | // enumerable property = _salary 39 | // enumerable property = name 40 | -------------------------------------------------------------------------------- /src/gist/gist-class-decorator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | Object.defineProperty(exports, "__esModule", { value: true }); 9 | function logClass(target) { 10 | // save a reference to the original constructor 11 | var original = target; 12 | // a utility function to generate instances of a class 13 | function construct(constructor, args) { 14 | var c = function () { 15 | return constructor.apply(this, args); 16 | }; 17 | c.prototype = constructor.prototype; 18 | return new c(); 19 | } 20 | // the new constructor behaviour 21 | var f = function () { 22 | var args = []; 23 | for (var _i = 0; _i < arguments.length; _i++) { 24 | args[_i] = arguments[_i]; 25 | } 26 | console.log("New: " + original['name'] + " is created"); 27 | return construct(original, args); 28 | }; 29 | // copy prototype so intanceof operator still works 30 | f.prototype = original.prototype; 31 | // return new constructor (will override original) 32 | return f; 33 | } 34 | exports.logClass = logClass; 35 | var Employee = /** @class */ (function () { 36 | function Employee() { 37 | } 38 | Employee = __decorate([ 39 | logClass 40 | ], Employee); 41 | return Employee; 42 | }()); 43 | var emp = new Employee(); 44 | console.log('emp instanceof Employee'); 45 | console.log(emp instanceof Employee); 46 | -------------------------------------------------------------------------------- /src/gist/gist-class-decorator.ts: -------------------------------------------------------------------------------- 1 | export function logClass(target: Function) { 2 | // save a reference to the original constructor 3 | const original = target; 4 | 5 | // a utility function to generate instances of a class 6 | function construct(constructor, args) { 7 | const c: any = function () { 8 | return constructor.apply(this, args); 9 | } 10 | c.prototype = constructor.prototype; 11 | return new c(); 12 | } 13 | 14 | // the new constructor behaviour 15 | const f: any = function (...args) { 16 | console.log(`New: ${original['name']} is created`); 17 | return construct(original, args); 18 | } 19 | 20 | // copy prototype so intanceof operator still works 21 | f.prototype = original.prototype; 22 | 23 | // return new constructor (will override original) 24 | return f; 25 | } 26 | 27 | @logClass 28 | class Employee { 29 | 30 | } 31 | 32 | let emp = new Employee(); 33 | console.log('emp instanceof Employee'); 34 | console.log(emp instanceof Employee); -------------------------------------------------------------------------------- /src/gist/gist-decorator.ts: -------------------------------------------------------------------------------- 1 | import { logClass } from './class-decorator'; 2 | import { logMethod } from './method-decorator'; 3 | import { logProperty } from './property-decorator'; 4 | import { logParameter } from './parameter-decorator'; 5 | 6 | export function log(...args) { 7 | switch (args.length) { 8 | case 3: 9 | if (typeof args[2] === "number") { 10 | return logParameter.apply(this, args); 11 | } 12 | return logMethod.apply(this, args); 13 | case 2: 14 | return logProperty.apply(this, args); 15 | case 1: 16 | return logClass.apply(this, args); 17 | default: 18 | throw new Error('Not a valid decorator'); 19 | } 20 | } 21 | 22 | @log 23 | class Employee { 24 | 25 | @log 26 | private name: string; 27 | 28 | constructor(name: string) { 29 | this.name = name; 30 | } 31 | 32 | @log 33 | greet(@log message: string): string { 34 | return `${this.name} says: ${message}`; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/gist/gist-method-decorator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || 3 | function (decorators, target, key, desc) { 4 | // function argument length 5 | var c = arguments.length 6 | 7 | /** 8 | * manipulating the result 9 | * if - only the array of decorators and target is passed 10 | * then - it should be an class decorator 11 | * else - if the descriptor (4th argument) is null 12 | * then - preparing the property descriptor with known values 13 | * otherwise - use same descriptor 14 | */ 15 | 16 | var r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc; 17 | 18 | // declaring this variable to hold the decorator 19 | var d; 20 | 21 | // if - native reflection part is available use that to trigger the decorators 22 | // else - fallback 23 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") { 24 | r = Reflect.decorate(decorators, target, key, desc) 25 | } 26 | else { 27 | // iterating through the decorators from right to left 28 | for (var i = decorators.length - 1; i >= 0; i--) { 29 | //if - decorator is valid assign to d 30 | if (d = decorators[i]) { 31 | /** 32 | * if - only the array of decorators and target is passed 33 | * then - it should be an class decorator - call the decorator by passing target 34 | * else - if all 4 arguments are there then its method decorator and call correspondingly 35 | * if argument passed is 3, then probably its property descriptor and call correspondingly 36 | * no condition satisfied - return the manipulated result 37 | */ 38 | r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r 39 | } 40 | } 41 | }; 42 | 43 | /** 44 | * since only the method decorator need to redefine its property with applied decorator result. 45 | * finally returning manipulated r 46 | */ 47 | return c > 3 && r && Object.defineProperty(target, key, r), r; 48 | }; 49 | 50 | exports.__esModule = true; 51 | function logMethod(target, propertyName, propertyDesciptor) { 52 | // target === Employee.prototype 53 | // propertyName === "greet" 54 | // propertyDesciptor === Object.getOwnPropertyDescriptor(Employee.prototype, "greet") 55 | var method = propertyDesciptor.value; 56 | propertyDesciptor.value = function () { 57 | var args = []; 58 | for (var _i = 0; _i < arguments.length; _i++) { 59 | args[_i] = arguments[_i]; 60 | } 61 | // convert list of greet arguments to string 62 | var params = args.map(function (a) { return JSON.stringify(a); }).join(); 63 | // invoke greet() and get its return value 64 | var result = method.apply(this, args); 65 | // convert result to string 66 | var r = JSON.stringify(result); 67 | // display in console the function call details 68 | console.log("Call: " + propertyName + "(" + params + ") => " + r); 69 | // return the result of invoking the method 70 | return result; 71 | }; 72 | return propertyDesciptor; 73 | } 74 | exports.logMethod = logMethod; 75 | ; 76 | var Employee = /** @class */ (function () { 77 | function Employee(firstName, lastName) { 78 | this.firstName = firstName; 79 | this.lastName = lastName; 80 | } 81 | Employee.prototype.greet = function (message) { 82 | return this.firstName + " " + this.lastName + " says: " + message; 83 | }; 84 | 85 | // typescript calls helper `__decorate` 86 | // to apply the decorator on the object's prototype 87 | __decorate([ 88 | logMethod 89 | ], Employee.prototype, "greet"); 90 | return Employee; 91 | }()); 92 | var emp = new Employee('Mohan Ram', 'Ratnakumar'); 93 | emp.greet('hello'); 94 | -------------------------------------------------------------------------------- /src/gist/gist-method-decorator.ts: -------------------------------------------------------------------------------- 1 | 2 | export function logMethod( 3 | target: Object, 4 | propertyName: string, 5 | propertyDesciptor: PropertyDescriptor): PropertyDescriptor { 6 | // target === Employee.prototype 7 | // propertyName === "greet" 8 | // propertyDesciptor === Object.getOwnPropertyDescriptor(Employee.prototype, "greet") 9 | const method = propertyDesciptor.value; 10 | 11 | propertyDesciptor.value = function (...args: any[]) { 12 | 13 | // convert list of greet arguments to string 14 | const params = args.map(a => JSON.stringify(a)).join(); 15 | 16 | // invoke greet() and get its return value 17 | const result = method.apply(this, args); 18 | 19 | // convert result to string 20 | const r = JSON.stringify(result); 21 | 22 | // display in console the function call details 23 | console.log(`Call: ${propertyName}(${params}) => ${r}`); 24 | 25 | // return the result of invoking the method 26 | return result; 27 | } 28 | return propertyDesciptor; 29 | }; 30 | 31 | class Employee { 32 | 33 | constructor( 34 | private firstName: string, 35 | private lastName: string 36 | ) { 37 | } 38 | 39 | @logMethod 40 | greet(message: string): string { 41 | return `${this.firstName} ${this.lastName} says: ${message}`; 42 | } 43 | 44 | } 45 | 46 | const emp = new Employee('Mohan Ram', 'Ratnakumar'); 47 | emp.greet('hello'); -------------------------------------------------------------------------------- /src/gist/gist-parameter-decorator.js: -------------------------------------------------------------------------------- 1 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 2 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 3 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 4 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 5 | return c > 3 && r && Object.defineProperty(target, key, r), r; 6 | }; 7 | var __param = (this && this.__param) || function (paramIndex, decorator) { 8 | return function (target, key) { decorator(target, key, paramIndex); } 9 | }; 10 | function logParameter(target, propertyName, index) { 11 | // generate metadatakey for the respective method 12 | // to hold the position of the decorated parameters 13 | var metadataKey = "log_" + propertyName + "_parameters"; 14 | if (Array.isArray(target[metadataKey])) { 15 | target[metadataKey].push(index); 16 | } 17 | else { 18 | target[metadataKey] = [index]; 19 | } 20 | } 21 | var Employee = /** @class */ (function () { 22 | function Employee() { 23 | } 24 | Employee.prototype.greet = function (message) { 25 | return "hello " + message; 26 | }; 27 | __decorate([ 28 | __param(0, logParameter) 29 | ], Employee.prototype, "greet"); 30 | return Employee; 31 | }()); 32 | var emp = new Employee(); 33 | emp.greet('hello'); 34 | -------------------------------------------------------------------------------- /src/gist/gist-parameter-decorator.ts: -------------------------------------------------------------------------------- 1 | function logParameter(target: Object, propertyName: string, index: number) { 2 | 3 | // generate metadatakey for the respective method 4 | // to hold the position of the decorated parameters 5 | const metadataKey = `log_${propertyName}_parameters`; 6 | if (Array.isArray(target[metadataKey])) { 7 | target[metadataKey].push(index); 8 | } 9 | else { 10 | target[metadataKey] = [index]; 11 | } 12 | } 13 | 14 | class Employee { 15 | greet(@logParameter message: string): string { 16 | return `hello ${message}`; 17 | } 18 | } 19 | const emp = new Employee(); 20 | emp.greet('hello'); 21 | -------------------------------------------------------------------------------- /src/gist/gist-property-decorator.js: -------------------------------------------------------------------------------- 1 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 2 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 3 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 4 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 5 | return c > 3 && r && Object.defineProperty(target, key, r), r; 6 | }; 7 | function logParameter(target, propertyName) { 8 | // property value 9 | var _val = this[propertyName]; 10 | // property getter method 11 | var getter = function () { 12 | console.log("Get: " + propertyName + " => " + _val); 13 | return _val; 14 | }; 15 | // property setter method 16 | var setter = function (newVal) { 17 | console.log("Set: " + propertyName + " => " + newVal); 18 | _val = newVal; 19 | }; 20 | // Delete property. 21 | if (delete this[propertyName]) { 22 | // Create new property with getter and setter 23 | Object.defineProperty(target, propertyName, { 24 | get: getter, 25 | set: setter, 26 | enumerable: true, 27 | configurable: true 28 | }); 29 | } 30 | } 31 | var Employee = /** @class */ (function () { 32 | function Employee() { 33 | } 34 | __decorate([ 35 | logParameter 36 | ], Employee.prototype, "name"); 37 | return Employee; 38 | }()); 39 | var emp = new Employee(); 40 | emp.name = 'Mohan Ram'; // Set: name => Mohan Ram 41 | console.log(emp.name); // Get: name => Mohan Ram 42 | -------------------------------------------------------------------------------- /src/gist/gist-property-decorator.ts: -------------------------------------------------------------------------------- 1 | function logParameter(target: Object, propertyName: string) { 2 | 3 | // property value 4 | let _val = this[propertyName]; 5 | 6 | // property getter method 7 | const getter = () => { 8 | console.log(`Get: ${propertyName} => ${_val}`); 9 | return _val; 10 | }; 11 | 12 | // property setter method 13 | const setter = newVal => { 14 | console.log(`Set: ${propertyName} => ${newVal}`); 15 | _val = newVal; 16 | }; 17 | 18 | // Delete property. 19 | if (delete this[propertyName]) { 20 | 21 | // Create new property with getter and setter 22 | Object.defineProperty(target, propertyName, { 23 | get: getter, 24 | set: setter, 25 | enumerable: true, 26 | configurable: true 27 | }); 28 | } 29 | } 30 | 31 | class Employee { 32 | 33 | @logParameter 34 | name: string; 35 | 36 | } 37 | 38 | const emp = new Employee(); 39 | emp.name = 'Mohan Ram'; 40 | console.log(emp.name); 41 | // Set: name => Mohan Ram 42 | // Get: name => Mohan Ram 43 | // Mohan Ram -------------------------------------------------------------------------------- /src/gist/metadata-reflection-api.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { 3 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; 4 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); 5 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; 6 | return c > 3 && r && Object.defineProperty(target, key, r), r; 7 | }; 8 | var __param = (this && this.__param) || function (paramIndex, decorator) { 9 | return function (target, key) { decorator(target, key, paramIndex); } 10 | }; 11 | exports.__esModule = true; 12 | require("reflect-metadata"); 13 | // parameter decorator uses reflect api to store the indexes of the decorated parameter 14 | function logParameter(target, propertyName, index) { 15 | var indices = Reflect.getMetadata("log_" + propertyName + "_parameters", target, propertyName) || []; 16 | indices.push(index); 17 | Reflect.defineMetadata("log_" + propertyName + "_parameters", indices, target, propertyName); 18 | } 19 | exports.logParameter = logParameter; 20 | // property decorator uses reflect api to get the run time type of the property 21 | function logProperty(target, propertyName) { 22 | var t = Reflect.getMetadata("design:type", target, propertyName); 23 | console.log(propertyName + " type: " + t.name); 24 | } 25 | exports.logProperty = logProperty; 26 | var Employee = /** @class */ (function () { 27 | function Employee(name) { 28 | this.name = name; 29 | } 30 | Employee.prototype.greet = function (message) { 31 | return this.name + " says: " + message; 32 | }; 33 | __decorate([ 34 | logProperty 35 | ], Employee.prototype, "name"); 36 | __decorate([ 37 | __param(0, logParameter) 38 | ], Employee.prototype, "greet"); 39 | return Employee; 40 | }()); 41 | -------------------------------------------------------------------------------- /src/gist/metadata-reflection-api.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | 3 | // parameter decorator uses reflect api to store the indexes of the decorated parameter 4 | export function logParameter(target: Object, propertyName: string, index: number) { 5 | const indices = Reflect.getMetadata(`log_${propertyName}_parameters`, target, propertyName) || []; 6 | indices.push(index); 7 | Reflect.defineMetadata(`log_${propertyName}_parameters`, indices, target, propertyName); 8 | } 9 | 10 | // property decorator uses reflect api to get the run time type of the property 11 | export function logProperty(target: Object, propertyName: string): void { 12 | var t = Reflect.getMetadata("design:type", target, propertyName); 13 | console.log(`${propertyName} type: ${t.name}`); 14 | } 15 | 16 | 17 | class Employee { 18 | 19 | @logProperty 20 | private name: string; 21 | 22 | constructor(name: string) { 23 | this.name = name; 24 | } 25 | 26 | greet(@logParameter message: string): string { 27 | return `${this.name} says: ${message}`; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { logMethod, configureMethod } from './method-decorator'; 2 | import { logProperty } from './property-decorator'; 3 | import { logParameter } from './parameter-decorator'; 4 | import { logClass } from './class-decorator'; 5 | import { log } from './decorators'; 6 | 7 | 8 | class JoiningKit { 9 | 10 | } 11 | 12 | @logClass 13 | class Employee { 14 | 15 | @logProperty 16 | private firstName: string; 17 | private lastName: string; 18 | 19 | @logProperty 20 | private joiningKit: JoiningKit; 21 | 22 | @logProperty 23 | private age: number; 24 | 25 | constructor(firstName: string, lastName: string) { 26 | this.firstName = firstName; 27 | this.lastName = lastName; 28 | } 29 | 30 | @logMethod 31 | @configureMethod({ enumerable: false }) 32 | greet(@logParameter message: string): string { 33 | return `${this.firstName} ${this.lastName} says: ${message}`; 34 | } 35 | 36 | getJoiningKit() { 37 | return this.joiningKit; 38 | } 39 | 40 | getAge() { 41 | return this.age; 42 | } 43 | 44 | setJoiningKit(joiningKit: JoiningKit) { 45 | return this.joiningKit = joiningKit; 46 | } 47 | } 48 | 49 | const emp = new Employee('Mohan Ram', 'Ratnakumar'); 50 | emp.greet('hello'); 51 | emp.getJoiningKit(); 52 | emp.getAge(); 53 | for (let i in emp) { 54 | console.log(i); 55 | } 56 | 57 | @log 58 | class Person { 59 | 60 | constructor( 61 | private firstName: string, 62 | private lastName: string 63 | ) { 64 | } 65 | 66 | @log 67 | getFullName() { 68 | return `${this.firstName} ${this.lastName}`; 69 | } 70 | } 71 | 72 | const person = new Person('Mohan', 'Ram'); 73 | person.getFullName(); -------------------------------------------------------------------------------- /src/method-decorator.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | 3 | export function configureMethod( 4 | { enumerable = true, configurable = true, writable = true }: { [key: string]: boolean } 5 | ) { 6 | return function ( 7 | target: Object, 8 | propertyName: string, 9 | propertyDesciptor: PropertyDescriptor 10 | ): PropertyDescriptor { 11 | propertyDesciptor.enumerable = enumerable; 12 | propertyDesciptor.configurable = configurable; 13 | propertyDesciptor.writable = writable; 14 | console.log(propertyDesciptor); 15 | return propertyDesciptor; 16 | } 17 | } 18 | export function logMethod( 19 | target: Object, 20 | propertyName: string, 21 | propertyDesciptor: PropertyDescriptor): PropertyDescriptor { 22 | // target === Employee.prototype 23 | // propertyName === "greet" 24 | // propertyDesciptor === Object.getOwnPropertyDescriptor(Employee.prototype, "greet") 25 | const method = propertyDesciptor.value; 26 | 27 | propertyDesciptor.value = function (...args: any[]) { 28 | 29 | //const indices = Reflect.getMetadata(`log_${propertyName}_parameters`, target, propertyName); 30 | const indices = target[`log_${propertyName}_parameters`]; 31 | if (Array.isArray(indices)) { 32 | args.forEach((arg, index) => { 33 | if (indices.indexOf(index) !== -1) { 34 | var argStr = JSON.stringify(arg) || arg.toString(); 35 | console.log(`logged parameters arg[${index}]: ${argStr}`); 36 | } 37 | }); 38 | } 39 | // convert list of foo arguments to string 40 | const params = args.map(a => JSON.stringify(a)).join(); 41 | 42 | // invoke foo() and get its return value 43 | const result = method.apply(this, args); 44 | 45 | // convert result to string 46 | const r = JSON.stringify(result); 47 | 48 | // display in console the function call details 49 | console.log(`Call: ${propertyName}(${params}) => ${r}`); 50 | 51 | // return the result of invoking the method 52 | return result; 53 | } 54 | return propertyDesciptor; 55 | }; 56 | -------------------------------------------------------------------------------- /src/parameter-decorator.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | 3 | export function logParameter(target: Object, propertyName: string, index: number) { 4 | const metadataKey = `log_${propertyName}_parameters`; 5 | if (Array.isArray(target[metadataKey])) { 6 | target[metadataKey].push(index); 7 | } 8 | else { 9 | target[metadataKey] = [index]; 10 | } 11 | /* const indices = Reflect.getMetadata(`log_${propertyName}_parameters`, target, propertyName) || []; 12 | indices.push(index); 13 | Reflect.defineMetadata(`log_${propertyName}_parameters`, indices, target, propertyName); */ 14 | } -------------------------------------------------------------------------------- /src/property-decorator.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | 3 | export function logProperty(target: Object, propertyName: string): void { 4 | var t = Reflect.getMetadata("design:type", target, propertyName); 5 | console.log(`${propertyName} type: ${t.name}`); 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src/*.ts", 4 | "src/**/*.ts" 5 | ], 6 | "exclude": [ 7 | "./dist", 8 | "./node_modules", 9 | "./src/gist" 10 | ], 11 | "compilerOptions": { 12 | "rootDir": "./src", 13 | "outDir": "./dist/", 14 | "noImplicitAny": false, 15 | "sourceMap": true, 16 | "module": "commonjs", 17 | "target": "es5", 18 | "experimentalDecorators": true, 19 | "emitDecoratorMetadata": true, 20 | } 21 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: './src/index.ts', 6 | output: { 7 | filename: 'bundle.js', 8 | path: path.resolve(__dirname, 'dist') 9 | }, 10 | devtool: 'inline-source-map', 11 | devServer: { 12 | contentBase: './', 13 | watchContentBase: true 14 | }, 15 | plugins: [ 16 | new webpack.HotModuleReplacementPlugin() 17 | ], 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.ts?$/, 22 | use: 'ts-loader', 23 | exclude: [/node_modules/, /gist/] 24 | } 25 | ] 26 | }, 27 | resolve: { 28 | extensions: ['.ts', '.js'] 29 | }, 30 | mode: 'development' 31 | }; --------------------------------------------------------------------------------