import { View } from 'angular2/angular2';
@View({
template: '<h1>test</h1>'
})
class AppComponent {
// ...
}
function View(settings) {
return function(target) {
target.template = settings.template;
// ...
return target;
}
}
@View({
template: '<h1>test</h1>'
})
class AppComponent {
// ...
}
The TypeScript compiler uses a polyfill so we can use decorators today.
// Class without decorators
class AppComponent1 {
// ...
}
// Class with decorators
@View({
template: '<h1>test</h1>'
})
class AppComponent2 {
// ...
}
// Class without decorators
var AppComponent1 = (function () {
function AppComponent1() {
}
return AppComponent1;
})();
// Class with decorators
var AppComponent2 = (function () {
function AppComponent2() {
}
AppComponent2 = __decorate([
View({
template: '<h1>test</h1>'
})
], AppComponent2);
return AppComponent2;
})();
There are 4 kinds of decorators:
declare type ClassDecorator =( target: TFunction ) => TFunction | void;
declare type PropertyDecorator = ( target: Object, propertyKey: string | symbol ) => void;
declare type MethodDecorator =( target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor ) => TypedPropertyDescriptor | void;
declare type ParameterDecorator = ( target: Object, propertyKey: string | symbol, parameterIndex: number ) => void;
@logClass
class Person {
@logProperty
public name: string;
public surname: string;
constructor(
name : string,
surname : string
) {
this.name = name;
this.surname = surname;
}
@logMethod
public saySomething(
@logParameter something : string
) : string {
return this.name + " " +
this.surname +
" says: " + something;
}
}
A class decorator function is a function that accepts a constructor function as its argument, and returns either undefined, the provided constructor function, or a new constructor function.
function logClass(target: any) {
// save a reference to the original constructor
var original = target;
// a utility function to generate instances of a class
function construct(constructor, args) {
var c : any = function () {
return constructor.apply(this, args);
}
c.prototype = constructor.prototype;
return new c();
}
// the new constructor behaviour
var f : any = function (...args) {
console.log("New: " + original.name);
return construct(original, args);
}
// copy prototype so intanceof operator still works
f.prototype = original.prototype;
// return new constructor (will override original)
return f;
}
A method decorator function is a function that accepts three arguments: The object that owns the property, the key for the property (a string or a symbol) and an optional property descriptor.
The function must return either undefined, the provided property descriptor, or a new property descriptor. Returning undefined is equivalent to returning the provided property descriptor.
function logMethod(target, key, descriptor) {
// save a reference to the original method this way we keep the values currently in the
// descriptor and don't overwrite what another decorator might have done to the descriptor.
if(descriptor === undefined) {
descriptor = Object.getOwnPropertyDescriptor(target, key);
}
var originalMethod = descriptor.value;
//editing the descriptor/value parameter
descriptor.value = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i - 0] = arguments[_i];
}
var a = args.map(function (a) { return JSON.stringify(a); }).join();
// note usage of originalMethod here
var result = originalMethod.apply(this, args);
var r = JSON.stringify(result);
console.log("Call: " + key + "(" + a + ") => " + r);
return result;
};
// return edited descriptor as opposed to overwriting the descriptor
return descriptor;
}
A property decorator function is a function that accepts two arguments: The object that owns the property and the key for the property (a string or a symbol). The return value of this decorator is ignored.
function logProperty(target: any, key: string) {
// property value
var _val = this[key];
// property getter
var getter = function () {
console.log(`Get: ${key} => ${_val}`);
return _val;
};
// property setter
var setter = function (newVal) {
console.log(`Set: ${key} => ${newVal}`);
_val = newVal;
};
// Delete property.
if (delete this[key]) {
// Create new property with getter and setter
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
}
A parameter decorator function is a function that accepts three arguments: The function that contains the decorated parameter, the property key of the member (or undefined for a parameter of the constructor), and the ordinal index of the parameter. The return value of this decorator is ignored.
function logParameter(target: any, key : string, index : number) {
var metadataKey = `__log_${key}_parameters`;
if (Array.isArray(target[metadataKey])) {
target[metadataKey].push(index);
} else {
target[metadataKey] = [index];
}
}
Parameter decorator must be
used in combination with a method decorator.
class Person {
// ...
@logMethod
public saySomething(@logParameter something : string, somethingElse : string) : string {
return this.name + " " + this.surname + " says: " + something + " " + somethingElse;
}
}
It is also recommended to use the reflect-metadata API instead of using class properties.
function logParameter(target: any, key: string, index: number) {
var metadataKey = `__log_${key}_parameters`;
var indices = Reflect.getMetadata(metadataKey, target, key) || [];
indices.push(index);
Reflect.defineMetadata(metadataKey, indices, target, key);
}
We can allow developers to pass arguments to a decorator when it is consumed:
@logClassWithArgs({ when : { name : "remo"} })
class Person {
public name: string;
// ...
}
function logClassWithArgs(filter: Object) {
return (target: Object) => {
// implement class decorator here, the class decorator
// will have access to the decorator arguments (filter)
// because they are stored in a closure
}
}
A decorator factory is used to improve the user experience of consuming a decorator.
function log(...args : any[]) {
switch(args.length) {
case 1:
return logClass.apply(this, args);
case 2:
return logProperty.apply(this, args);
case 3:
if(typeof args[2] === "number") {
return logParameter.apply(this, args);
}
return logMethod.apply(this, args);
default:
throw new Error();
}
}
@log
class Person {
@log
public name: string;
public surname: string;
constructor(
name : string,
surname : string
) {
this.name = name;
this.surname = surname;
}
@log
public saySomething(
@log something : string
) : string {
return this.name + " " +
this.surname +
" says: " + something;
}
}
Is an external static dictionary to read and write metadata. We need to use the --emitDecoratorMetadata compiler options and the reflect-metadata npm package.
function logParamTypes(target : any, key : string) {
var types = Reflect.getMetadata("design:paramtypes", target, key);
var s = types.map(a => a.name).join();
console.log(`${key} param types: ${s}`);
}
class Foo {}
interface IFoo {}
class Demo{
@logParameters
doSomething(
param1 : string,
param2 : number,
param3 : Foo,
param4 : { test : string },
param5 : IFoo,
param6 : Function,
param7 : (a : number) => void,
) : number {
return 1
}
}
// doSomething param types: String, Number, Foo, Object, Object, Function, Function
Inversion of control (IoC) containers
import { Inject } from 'angular2/angular2';
@Inject(Engine, Tires, Doors)
class Car {
constructor(
engine,
tires,
doors
) {
...
}
}
Many other applications
import { Component } from 'angular2/angular2';
@Component({
selector: 'app',
template: '<h1>test</h1>'
})
class App {
constructor() {
this.name = 'World';
}
}