JavaScript-TypeScript — Decorators

What is a Decorator?

A decorator is simply a way of wrapping a function with another function to extend its existing capabilities. You “decorate” your existing code by wrapping it with another piece of code. This concept will not be new to those who are familiar with functional composition or higher-order functions.

Why Use a Decorator?

Decorators allow you to write cleaner code and achieve composition. It also helps you extend the same functionality to several functions and classes. Thereby enabling you to write code that is easier to debug and maintain.

"target": "es6", "experimentalDecorators": true,
// Decorator Function
function Logger(constructor: Function) {
console.log("Logger...."); console.log(constructor);}@Loggerclass Person { name = "Amir"; constructor() { console.log("Constructor called.."); }}const pers = new Person();console.log(pers);
function Logger5(status: boolean = false, data:number = null) {  return function (constructor: Function) {   if(status) {
// Some logic..
console.log("Logger...."); console.log(anydata); console.log(constructor);
}
};}const status = true;const data = 100;@Logger5(status, data) // Passing some dataclass Person5 { name = "Amir"; constructor() { console.log("Constructor called.."); }}const pers5 = new Person5();console.log(pers5);
// Decorator Factory
function WithTemplate(template: string, hookId: string) {
return function (_: Function) { // _ tells TS to ignore const hookEl = document.getElementById(hookId); if (hookEl) { hookEl.innerHTML = template; } };}@WithTemplate("<h2>My Person Object</h2>", "app")class Person5 { name = "Amir"; constructor() { console.log("Constructor called.."); }}const pers5 = new Person5();console.log(pers5);
<!DOCTYPE HTML>  <html>    <head>      <title>My test page</title>      <script src="dist/app.js" defer></script>    </head>    <body>       <h1 id="app"></h1>    </body></html>
function WithTemplate(template: string, hookId: string) {  return function (constructor: any) {    const hookEl = document.getElementById(hookId);    const p = new constructor();  // Class instance created    if (hookEl) {      hookEl.innerHTML = template + p.name;  // Accessing class data    }  };}@WithTemplate("<h2>My Person Object</h2>", "app")  class Person6 {    name = "Amir";    // data    constructor() {       console.log("Constructor called..");    }  }const pers6 = new Person6();console.log(pers6);
Angular Decorator
order in which factory and decorators called
// Decorator Factoryfunction Logger7() {  console.log("1. LOGGER: Factory");  // Decorator Function  return function (constructor: Function) {    console.log("2. LOGGER: Decorator");    console.log(constructor);  };}// Decorator Factoryfunction WithTemplate7(template: string, hookId: string) {  console.log("3. TEMPLATE: Factory");  // Decorator Function  return function (constructor: any) {    console.log("4. TEMPLATE: Decorator");    const hookEl = document.getElementById(hookId);    const p = new constructor();    if (hookEl) {      hookEl.innerHTML = template + p.name;    }  };}// Decorator Call@Logger7()@WithTemplate7("<h2>My Person Object</h2>", "app")class Person7 {  name = "Amir";  constructor() {    console.log("Constructor called..");  }}const pers7 = new Person7();console.log(pers7);
// Property Decorator
function Log(target: any, propertyName: string | Symbol) {
console.log("Property decorator"); console.log(target, propertyName);}class Product { @Log // Log decorator is for string property title: string; private _price: number; set price(val: number) { if (val > 0) { this._price = val; } else { throw new Error("Invalid price - should be positive"); } } constructor(t: string, p: number) { this.title = t; this._price = p; } getPriceWithTax(tax: number) { return this._price * (1 + tax);
}
}
// Property Decorator
function Log(target: any, propertyName: string | Symbol) {
console.log("Property decorator"); console.log(target, propertyName);}// Accessor Decoratorfunction Log2(target: any, name: string, descriptor: PropertyDescriptor) { console.log("Log2 ======>", "Property Descriptor"); console.log("target", target); console.log("name", name); console.log("descriptor", descriptor);}class Product { @Log title: string; private _price: number; @Log2 // With Accessor set price(val: number) { if (val > 0) { this._price = val; } else { throw new Error("Invalid price - should be positive"); }}
function Log(target: any, propertyName: string | Symbol) {  console.log("Log1 ======>", "Property Decorator");  console.log("target", target);  console.log("name", propertyName);  console.log("--------------------------------------------");}function Log2(target: any, name: string, descriptor: PropertyDescriptor) {  console.log("Log2 ======>", "Accessor Decorator");  console.log("target", target);  console.log("name", name);  console.log("descriptor", descriptor);  console.log("--------------------------------------------");}// Method Decoratorfunction Log3(target: any, name: string | Symbol, descriptor: PropertyDescriptor) {  console.log("Log3 ======>", "Method Decorator");  console.log("target", target);  console.log("name", name);  console.log("descriptor", descriptor);  console.log("--------------------------------------------");}class Product {  @Log    // Property Decorator (i.e. for string)  title: string;  private _price: number;  @Log2  // Access Decorator (i.e. for _price)  set price(val: number) {    if (val > 0) {      this._price = val;    } else {      throw new Error("Invalid price - should be positive");    } }  constructor(t: string, p: number) {    this.title = t;    this._price = p;  }  @Log3 // Method Modifier (i.e. for getPriceWithTax)  getPriceWithTax(tax: number) {    return this._price * (1 + tax);  }}let product = new Product("Desk", 100);
function Log4(target: any, name: string | Symbol, position: number){  console.log("Log4 ======>", "Paramater Decorator");  console.log("target", target);  console.log("name", name);  console.log("position", position);  console.log("----------------------------- ---------------");}
class Product {
title: string; private _price: number; getPriceWithTax(@Log4 tax: number) { return this._price * (1 + tax); }}let product = new Product("Desk", 100);
function Template(template: string, hookId: string) {  return function <T extends { new (...args: any[]): { name: string } }>(originalConstructor: T) {  return class extends originalConstructor { // anonymousclass retrn    constructor(..._: any[]) {    //_ ts will know, that it gets         parameter but will not use it       super();       const hookEl = document.getElementById(hookId);       if (hookEl) {        hookEl.innerHTML = template;        hookEl.querySelector("h3")!.innerHTML += this.name;       }    }  }; };}// Class Decorator
@Template("<h3>My template</h3>", "app")
class Productt { name = "Amir"; constructor() { console.log("Constructor called.."); }}let productt = new Productt(); // instantiating is must for constructor to run
<!DOCTYPE HTML>  <html>  <head>    <title>My test page</title>    <script src="dist/app.js" defer></script>  </head>  <body>    <h3 id="app">Typescript</h3>    <button>Click me!</button>  </body></html>
class Printer {  message = "This works";  showMessage() {    console.log(this.message);  }}const p = new Printer();const btn = document.querySelector("button")!;btn.addEventListener("click", p.showMessage);  // O/p is undefined as this is not pointing to class
class Printer {  message = "This works";  showMessage() {    console.log(this.message);  }}const p = new Printer();const btn = document.querySelector("button")!;btn.addEventListener("click", p.showMessage.bind(p));  // O/p This works
function Autobind(_target: any,_methodname: string,descriptor: PropertyDescriptor) {  const originalMethod = descriptor.value;  const adjDescriptor: PropertyDescriptor = {    configurable: true,    enumerable: false,    // removed value, added our getter which will bind
get
() {
const boundFn = originalMethod.bind(this); // this inside get points to obj return boundFn; }, }; return adjDescriptor;}class Printer { message = "This works"; @Autobind showMessage() { console.log(this.message); }}const p = new Printer();const btn = document.querySelector("button")!;btn.addEventListener("click", p.showMessage);
Angular Decorator
//Before decorator
class MyApp extends React.Component {
// ...define your main app here
}
export default connect(mapStateToProps, mapDispatchToProps)(MyApp);//After decorator
@connect(mapStateToProps, mapDispatchToProps)
export default class MyApp extends React.Component {
// ...define your main app here
}

Closing thoughts:

Decorators are a powerful tool that enables you to create code that is very flexible. There is a very high chance you will come across them quite often in the near future.

Web Artisan. Human — Engineering.