[Typescript] decorator example

Language/Typescript 2017.12.28 20:36


Working environment:
OS: Linux 4.4.0-104-generic #127-Ubuntu SMP Mon Dec 11 12:16:42 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
nodejs: 8.9.1
typescript: 2.6.2




<< class decorator >>


(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Class decorator
//
///////////////////////////////////////////////////////////////////////////////
/**
 * See ClassDecorator type at lib.es5.d.ts
 * @param ctor constructor function. Prototype of constructor function
 *               is object for class-definition.
 */
function log<T extends Function>(ctor: T): T | void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);
    console.assert('function' === typeof(ctor));
    console.log('%o', ctor);
    let newConstructor: any = function (...args: Array<any>) {
        console.log('New: ' + ctor.name);
        let o: any = new (<any>ctor)(...args);
        // this is new constructor. So, '_test' is shown at console.log(<instance>)
        o._test = 'mytest';
        return o;
    };
    return newConstructor;
}

@log
class Person {
    constructor(
        public name: string,
        public surname: string
    ) { }

    get myname() {
        return this.name;
    }

    fullname(nick: string) {
        return `${this.name} ${this.surname} : ${nick}`;
    }
}

let p = new Person('name', 'surname');
// console.log('%o, %O', p, p);
console.assert(p.name === 'name');
console.assert(p.myname === 'name');
console.assert(p.fullname('N') === 'name surname : N');
console.assert((<any>p)._test === 'mytest');


///////////////////////////////////////////////////////////////////////////////
//
// Class decorator with argument.
//
///////////////////////////////////////////////////////////////////////////////
function logArg(nick: string) {
    return function<T extends Function>(ctor: T): T | void {
        let newCtor: any = function (...args: Array<any>) {
            // console.log('New: ' + fconstructor.name);
            let o: any = new (<any>ctor)(...args);
            o.nick = nick;
            return o;
        };
        return newCtor;
    };
}

@logArg('nick')
class Person2 {
    constructor(
        public name: string
    ) { }

    fullname(age: number) {
        return `${this.name} ${(<any>this).nick} : ${age}`;
    }
}

let p2 = new Person2('name');
// console.log(p);
console.assert(p2.name === 'name');
console.assert(p2.fullname(18) === 'name nick : 18');
console.assert((<any>p2).nick === 'nick');


///////////////////////////////////////////////////////////////////////////////
})();









<< method decorator >>

(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Member decorator
//
///////////////////////////////////////////////////////////////////////////////

/**
 * See MethodDecorator at lib.es5.d.ts for detail types.
 * @param target class(prototype of class-constructor) where this method belongs to
 * @param key name of method
 * @param descriptor method(function)-property descriptor
 */
function log(
    target: any, key: string | symbol, descriptor: TypedPropertyDescriptor<any>
): TypedPropertyDescriptor<any> | void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);

    console.assert('fullname' === key);

    // This is ONLY FOR DEMO and EXAMPLE! - NOT RECOMMENDED!
    // Add new property to prototype of class-constructor.
    // (applied to all class instance!)
    target['newattr'] = 'hello';

    // 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)!;
    }

    //editing the descriptor/value parameter
    let orig = descriptor.value;
    descriptor.value = function () {
        console.log(`Called: ${key}`);
        return orig!.apply(this, arguments);
    };

    return descriptor;
}


class Person {
    constructor(
        public name: string,
    ) { }

    get myname() {
        return this.name;
    }

    @log
    fullname(newname: string, nick: string) {
        this.name = newname;
        return `${this.name}:${nick}`;
    }
}

let p = new Person('name');
let p2 = new Person('name2');
// console.log(p.fullname('newname', 'nick'));
console.assert('name' === p.myname);
console.assert('name2' === p2.myname);
console.assert('hello' === (<any>p).newattr);
console.assert('hello' === (<any>p2).newattr);
console.assert('newname:nick' === p.fullname('newname', 'nick'));
console.assert('newname' === p.myname);
console.assert(`hello` === (<any>p).newattr);

console.log(p);

///////////////////////////////////////////////////////////////////////////////
//
// Member decorator with argument
//
///////////////////////////////////////////////////////////////////////////////

// Same way with class decorator.

///////////////////////////////////////////////////////////////////////////////
})();





<< parameter decorator >>

(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Parameter decorator
//
///////////////////////////////////////////////////////////////////////////////

/**
 *
 * @param target class(prototype of class-constructor) where this method belongs in
 * @param key name of method where this parameter belongs to
 * @param index parameter position in function - stars at 0(the first parameter).
 */
function log(target: any, key: string | symbol, index: number): void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);

    console.assert('fullname' === key);
    console.assert(1 === index);
}


class Person {
    constructor(
        public name: string,
    ) { }

    get myname() {
        return this.name;
    }

    fullname(newname: string, @log nick: string) {
        this.name = newname;
        return `${this.name}:${nick}`;
    }
}

let p = new Person('name');
// console.log(p.fullname('newname', 'nick'));
console.assert('newname:nick' === p.fullname('newname', 'nick'));
console.assert('newname' === p.myname);

///////////////////////////////////////////////////////////////////////////////
})();






<< property decorator >>

(() => {
///////////////////////////////////////////////////////////////////////////////
//
// Property decorator
//
///////////////////////////////////////////////////////////////////////////////

/**
 *
 * @param target class(prototype of class-constructor) where this method belongs in
 * @param key name of method where this parameter belongs to
 */
function log(target: any, key: string | symbol): void {
    // decorator is called with 'this' bound to global-object,
    console.assert(undefined !== this.console);
    console.assert(console === this.console);
    console.assert('name' === key || 'obj' === key);

    let internalKey = `$${key}`;
    // property getter
    let getter = function () {
        console.log(`Get: ${key} => ${this[internalKey]}`);
        return this[internalKey];
    };

    // property setter
    let setter = function (newVal: any) {
        console.log(`Set: ${key} => ${newVal}`);
        this[internalKey] = newVal;
    };

    // Delete property.
    if (delete target[key]) {
        console.assert(undefined === target[`$${key}`]);
        // Create new property with getter and setter
        Object.defineProperty(target, key, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}

class Person {
    @log
    public name = 'init';
    @log
    public obj = {};

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

    get myname() {
        return this.name;
    }

    fullname(newname: string, nick: string) {
        this.name = newname;
        return `${this.name}:${nick}`;
    }
}

let p = new Person('name');
p.name = 'hello';
p.obj = {a: 1};

console.assert((<any>p)['$name'] === p.name);
console.assert((<any>p)['$obj'] === p.obj);
console.assert('newname:nick' === p.fullname('newname', 'nick'));
console.assert('newname' === p.myname);

// console.log('%o', p);

///////////////////////////////////////////////////////////////////////////////
})();


Trackbacks 0 : Comments 0