---
# System prepended metadata

title: Typescript Decorator

---

# Typescript Decorator

Un decoratore è una funzione che prende la una classe e la arricchisce di specifiche funzionalità. Si tratta di uno standard introdotto dalle specifiche ECMAScript 2016 (ES7) e già recepite da TypeScript.

## Configurazione

Per prima cosa bisogna impostare all'interno del file `tsconfig.json` questa configurazione

```json
{
  "compilerOptions": {
     "target": "es2016",
     "experimentalDecorators": true
   }
}
```

## Tipologie

Ci sono 4 principali tipi di decorator:

- Class Decorator
- Property Decorator
- Method Decorator
- Parameter Decorator

### Class Decorator

È un decorator applicato ad una classe, riceve un parametro che rappresenta la classe da decorare.

```typescript
declare type ClassDecorator = <TFunction extends Function>
  (target: TFunction) => TFunction | void;
```

Un esempio easy è il seguente:

```typescript
function Greeter(target: Function): void {
  target.prototype.greet = function (): void {
    console.log("Ciao sono un utente!");
  };
}

@Greeter
class User {}

let user = new User();
(user as any).greet(); // -> "Ciao sono un utente!"
```

Il decorator `@Greeter` aggiunge alla classe sottostante il metodo `.greet()`.

Se vogliamo customizzare l'output tramite il decorator dobbiamo utilizzare il **Decorator Factory**, es:

```typescript
function Greeter(msg: string) {
   return (target: Function): void => {
    target.prototype.greet = function (): void {
        console.log(msg);
    };
   }
}

@Greeter("Hello TypeScript!")
class User {}

let user = new User();
(user as any).greet(); // -> "Hello TypeScript!"
```

### Property Decorator

È un decorator applicato ad una proprietà di una classe, riceve due parametri che rappresentano la classe e la proprietà da decorare.

```typescript
declare type PropertyDecorator = 
 (target: Object, propertyKey: string | symbol) => void;
```

Un esempio è il seguente:

```typescript
function logProperty(target: any, key: string) {
  let value = target[key]

  const getter = function () {
    console.log(`Getter for ${key} returned ${value}`)
    return value
  }

  const setter = function (newVal: any) {
    console.log(`Set ${key} to ${newVal}`)
    value = newVal
  }

  // Replace the property
  if (delete target[key]) {
    Object.defineProperty(target, key, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true,
    })
  }
}

class User {
  @logProperty
  public name: string

  constructor(name: string) {
    this.name = name
  }
}

const user = new User('Mario')
user.name = 'Luca'
console.log(user.name)
```

L'output in console sarebbe il seguente:

```bash
Set name to Mario // -> from setter
Set name to Luca // -> from setter
Getter for name returned Luca // -> from getter
Luca // -> from console.log and direct access
```

### Method Decorator

È un decorator applicato ad un meotodo di una classe, riceve tre parametri che rappresentano: la classe, il nome del metodo e un oggetto con le proprietà.

```typescript
declare type MethodDecorator = <T>(
  target: Object, 
  propertyKey: string | symbol, 
  descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;

interface TypedPropertyDescriptor<T> {
    enumerable?: boolean;
    configurable?: boolean;
    writable?: boolean;
    value?: T;
    get?: () => T;
    set?: (value: T) => void;
}
```

Un esempio è il seguente:

```typescript
function logMethod(
  target: Object,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  let originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Before invoking method: ${propertyKey}`);
    let result = originalMethod.apply(this, args);
    console.log(`After invoking method: ${propertyKey}`);
    return result;
  };
}

class User {
  @logMethod
  public greet(msg: string) {
    console.log(`Hello ${msg}!`)
  }
}

let user = new User()
user.greet('Mario')
```

L'output in console sarebbe il seguente:

```bash
Before invoking method: greet
Hello Mario!
After invoking method: greet
```

### Parameter Decorator

È un decorator applicato ad un parametro di un metodo, riceve tre parametri che rappresentano: la classe, il metodo e l'indice del parametro da decorare.

```typescript
declare type ParameterDecorator = (
 target: Object, 
 propertyKey: string | symbol, 
 parameterIndex: number) => void;
```

Un esempio è il seguente:

```typescript
function logParameter(target: Object, key: string, parameterIndex: number) {
  console.log(`The parameter in position ${parameterIndex} at ${key} has been decorated`);
}

class User {
  public greet(@logParameter msg: string): void {
    console.log(msg)
  }
}

let user = new User()
user.greet('Mario')
```

L'output in console sarebbe il seguente:

```bash
The parameter in position 0 at greet has been decorated
Mario
```

## Quindi?

Tutto bello ma usiamo i decorator solo per fare i saluti?

In questo [repository](https://github.com/NetanelBasal/helpful-decorators) si possono trovare molte implementazioni utili, es:

```typescript
class Test {
    @delay(1000)
    method() {
        // Questo metodo verrà invocato un ritardo di un secondo
    }
    
    @once
    method() {
        // Questo metodo verrà invocato una sola volta
    }
    
    @measure
    doSomething() {
        // Call to doSomething took 0.35 milliseconds.
    }
    
    @SortBy('', { isDescending: false, type: 'number'})
    numbers = [ 6, 3, 4, 1 ]; // in fase di get restituirà un array ordinato, es "[1, 3, 4, 6]"
}
```

Sulla VMApp abbiamo implementato diversi decorator, tipo:

### CacheDecorator

```typescript
import hash from 'object-hash'
import CacheService from 'Services/CacheService'

export default function CacheDecorator(seconds: number = 86400) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const method = descriptor.value
    descriptor.value = CacheWrapper(method.bind(target), seconds)
  }
}

function CacheWrapper<T extends Array<any>, U extends any>(
  fn: (...args: T) => Promise<U>,
  seconds?: number
) {
  return async function (...args: T): Promise<U> {
    const key = hash({ args })

    const cached = await CacheService.get(key)
    if (cached) {
      logger.info(`Returning cached data for key "${key}"`, { key })
      return cached.data
    }

    const data = await fn(...args)
    if (data !== undefined) {
      logger.info(`Setting new cached data for key "${key}"`, { key, data, seconds })
      CacheService.set(key, data, seconds)
    }

    return data
  }
}
```

```typescript
class Test {
    @CacheDecorator()
    public getSomething() {
        // ...
    }
}
```

### TransactionDecorator

```typescript
/**
 * Ensures that a transaction is passed to the method. Custom error can be passed
 * as optional argument.
 *
 * This decorator is supposed to be added only to instance methods where Transaction object
 * is the LAST ARGUMENT of the method signature. The decorator wraps the method inside
 * a function that check if a transaction is already started - i.e., is passed as the last argument
 * to the method call, or not. If no transaction is passed in, this function starts a new
 * transaction, calling the original method inside a try/catch group.
 *
 * Since this decorator wraps a single function call inside a transaction group, the decorator
 * is supposed to be called on a higher level than that of the single db queries. As an example
 * suppose we have three functions A, B, C, annotated with the TransactionDecorator,
 * all of them accepts a transaction as the last argument:
 *
 * @TransactionDecorator
 * function A(trx) {
 *   B(trx)
 *   C(trx)
 * }
 *
 * @TransactionDecorator
 * function B(trx) {
 *  // some query
 * }
 *
 * @TransactionDecorator
 * function C(trx) {
 *  // some query
 * }
 *
 * An A() call will init a new transaction and function B and C will receive it.
 * Yet in case B() or C() are called autonomously, the decorator will start a transaction
 * just for them singularly.
 *
 * @param {Error} customError
 */
export default function TransactionDecorator(customError?: Error) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const method = descriptor.value
    descriptor.value = async function (...args: any[]) {
      if (transactionAlreadyStarted(args)) {
        // no need to start a new transaction, exit continuing with method call
        // @ts-ignore
        return method.call(this, ...args)
      }
      // start a new transaction and wrap the method call within it
      const transaction = await SequelizeConnection.transaction()
      try {
        // @ts-ignore
        const res = await method.call(this, ...args, transaction)
        await transaction.commit()
        return res
      } catch (e) {
        logger.error(e?.message, { error: e })
        await transaction.rollback()
        throw customError ? customError : e
      }
    }
  }
}

/**
 * Returns true if and only if the last object of args array is of type transaction,
 * meaning that transaction is already started.
 *
 * @param {Array<any>} args
 */
function transactionAlreadyStarted(args: Array<any>): boolean {
  if (!args || !Array.isArray(args) || args.length <= 0) {
    return false
  }
  const lastArgument = args[args.length - 1]
  return lastArgument?.constructor?.name === 'Transaction'
}
```

```typescript
class Test {
    @TransactionDecorator()
    public doSomething() {
        // ...
    }
}
```

### InterfaceValidatorDecorator

```typescript
import { Schema } from 'joi'

export default (schema: Schema) => {
  return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
    const targetMethod = descriptor.value

    descriptor.value = async function (...args: any[]) {
      const res = await targetMethod.apply(this, args)
      const validation = schema.validate(res, { allowUnknown: true, stripUnknown: true })

      if (validation.error) {
        throw new Error(...)
      }

      return validation.value
    }

    return descriptor
  }
}
```

```typescript
class Test {
    @InterfaceValidatorDecorator(DepartmentKpiBiSchema)
    public async getDepartmentsKpi(...): Promise<Array<DepartmentKpiBiInterface>> {
        // ...
    }
}
```

```typescript
import Joi from 'joi'

export interface DepartmentKpiBiInterface {
  department_code: string
  department_desc: string
}

const DepartmentKpiBiSchema = Joi.array().items(
  Joi.object({
    department_code: Joi.string().trim(),
    department_desc: Joi.string(),
  })
)

export default DepartmentKpiBiSchema
```

...


Il limite è solo la vostra immaginazione...