Typescript Decorators | Typescript Tutorials

javascript Aug 11, 2019

When it comes to organizing code, most organization techniques are aligned either horizontally or vertically. A common horizontal organization technique is n-tier architecture, where the program is split into layers that handle the user interface, business logic, and data access. A rapidly growing vertical organization technique is micro-services, where each vertical slice represents a bounded context, such as “payments,” “customers,” or “users.


Decorators are relevant to both horizontal and vertical architectures, but can be particularly valuable in the context of vertical architectures. Decorators can be used to take care of cross-cutting concerns such as logging, authorization, or validation. When used correctly, this aspect-oriented style of programming can minimize the code required to satisfy these shared responsibilities


TypeScript decorators can be used for aspect-oriented programming (AOP) and meta-programming, but we’ll start with AOP as it provides a solid example of a real-world use of decorators.


The syntax for decorators is straightforward. code shows a decorator function, and a use of the decorator against the square method, with the decorator applied using the @ symbol.

// Decorator Function
function log(target: any, key: string, descriptor: any) {
	// square
	console.log(key);
}
class Calculator {
    // Using the decorator
    @log
    square(n: number) {
    	return n * n;
    }
}

Decorators can be applied to any of the following:
• Classes
• Accessors
• Properties
• Methods
• Parameters

Each kind of decorator requires a different function signature, as the decorator is provided with different parameters depending on the decorator use. This section will provide several practical examples that can be used as a starting point for your own decorators.
A more complete example of a property decorator is shown in code below. As well as passing the name of the method in the key parameter, a property descriptor is passed in the descriptor parameter. The property descriptor is an object that contains the original method and some metadata. The method itself is found within the value property of the descriptor. When a method decorator returns a value, the value will
be used as the descriptor. This means you can choose to observe, modify, or replace the original method.
In the case of the logging method decorator, the original method in the descriptor is wrapped with a logging function, which logs the fact the method was called as well as the arguments passed and the value returned. Each time the method is called, the information is logged.

function log(target: any, key: string, descriptor: any) {
    const original = descriptor.value;
    descriptor.value = function (...args: any[]) {
        // Call the original method
        const result = original.apply(this, args);
        // Log the call, and the result
        console.log(`${key} with args ${JSON.stringify(args)} returned
        ${JSON.stringify(result)}`);
        // Return the result
        return result;
    }
    return descriptor;
}
class Calculator {
// Using the decorator
    @log
    square(num: number) {
  	  return num * num;
    }
}
const calculator = new Calculator();
// square with args [2] returned 4
calculator.square(2);
// square with args [3] returned 9
calculator.square(3);

This basic logging example means that the code within the calculator class need know nothing of the logging within the program. The logging logic can be kept entirely separate from the rest of the code in the program, and the logging code would be solely responsible for when and where information is logged.

Configurable Decorators

You can make a decorator configurable by converting your decorator function into a decorator factory. A decorator factory is a function that returns a decorator function. The factory can have any number of parameters that can be used in the creation of the decorator.
the logging decorator has been converted into a decorator factory. The factory accepts a title that is prepended to the logging message.

function log(title: string) {
    return (target: any, key: string, descriptor: any) => {
        const original = descriptor.value;
            descriptor.value = function (...args: any[]) {
            // Call the original method
            const result = original.apply(this, args);
            // Log the call, and the result
            console.log(`${title}.${key}
            with args ${JSON.stringify(args)}
            returned ${JSON.stringify(result)}`);
            // Return the result
            return result;
            }
        return descriptor;
    };
}
class Calculator {
// Using the configurable decorator
    @log('Calculator')
    square(num: number) {
   		 return num * num;
    }
}
const calculator = new Calculator();
// Calculator.square with args [2] returned 4
calculator.square(2);
// Calculator.square with args [3] returned 9
calculator.square(3);

Class Decorators

To adapt the logging decorator for use with a class, the constructor must be wrapped with a logging constructor. This is slightly more complex than the method decorator, but the example in code can be quickly adapted from logging to some other purpose

function log(target: any) {
    const original = target;
    // Wrap the constructor with a logging constructor
    const constr: any = (...args) => {
    console.log(`Creating new ${original.name}`);
    const c: any = () => {
    return original.apply(null, args);
    }
    c.prototype = original.prototype;
    return new c();
    }
    constr.prototype = original.prototype;
    return constr;
}
@log
class Calculator {
    square(n: number) {
   	 return n * n;
    }
}
// Creating new Calculator
var calc1 = new Calculator();
// Creating new Calculator
var calc2 = new Calculator();

Neeraj Dana

Experienced Software Engineer with a demonstrated history of working in the information technology and services industry. Skilled in Angular, React, React-Native, Vue js, Machine Learning