TypeScript Decorators

By on October 20, 2015 7:33 am

TypeScript

One of the main benefits of working in TypeScript is that it lets developers use modern standards within their source code today. Tools like destructuring, rest and spread operations, and classes make it easier to define structures and work with data.

TypeScript also provides support for ES.Next Decorators, giving developers an important tool for defining frameworks and reusable structural patterns. Today we will explain what decorators are, how they can be used to improve your projects, and how to start using them in
TypeScript

What are Decorators?

Decorators are simply functions that modify a class, property, method, or method parameter. The syntax is an “@” symbol followed by a function.

@readonly
class Person {
    name: string;
    isAdmin: boolean;

    constructor(name:string, admin: boolean) {
        this.name = name;
        this.isAdmin = admin;
    }
}

The @readonly decorator is a class decorator; it is passed a reference to the constructor function and returns a new constructor function that extends its behavior.

function readonly<TFunction extends Function>(Target: TFunction): TFunction {
    let newConstructor = function () {
        Target.apply(this);
        Object.freeze(this);
    };

    newConstructor.prototype = Object.create(Target.prototype);
    newConstructor.prototype.constructor = Target;

    return <any> newConstructor;
}

With this very simple syntax we are able to apply a concept to any class or element. Decorators can be used to modify the behavior of a class or become even more powerful when integrated into a framework. For instance, if your framework has methods with restricted access requirements, it would be easy to write an @admin method decorator to deny access to non-administrative users, or an @owner decorator to only allow the owner of an object the ability to modify it.

class CRUD {
    get() { }
    post() { }

    @admin
    delete() { }

    @owner
    put() { }
}

In the above example, @admin and @owner would check against a singleton user object to see if the user had the proper privileges, and throw an exception if they did not. The specific implementation of each method would vary based on the conditions suitable for your application.

Decorators are a powerful mechanism for defining behaviors, eliminating boilerplate code, and building frameworks. They offer developers a way to describe functionality using higher-order functions that mutate and transform a class.

Using Decorators in TypeScript

Today, decorators are still a stage 1 proposal for the ES2016 specification. While there are several steps before this is accepted as part of the formal JavaScript language specification, thanks to transpilers like TypeScript and Babel projects can use them now to improve developer productivity.

To use decorators with TypeScript pass the --experimentalDecorators flag the TypeScript compiler or withing your tsconfig.json. It’s as simple as:

tsc --experimentalDecorators main.ts

Learning More

There are many examples on using decorators that can help you get started including an example decorator and metadata usage with Grunt and Intern support, Cocktailjs — an annotation library for Node.js, and Angular 2. Decorators are an exciting new feature and hold a lot of promise for improving your developer experience. We encourage you to start looking into them today!

We also have a much more in depth ES6 and TypeScript fundamentals workshop available. The workshop will be very focused on learning the most important features and nuances of ES6 and TypeScript in a short amount of time. To register, see our complete workshop schedule.

Or if you are starting to use ES6 and/or TypeScript in your development efforts today, and need a little help with decorators or other concepts, our Enterprise JavaScript Support may be an option to consider.

Contact us to discuss how we can help your organization learn more about ES6 and TypeScript.

Comments

  • Ricardo

    It seems pretty powerful to reuse code. Good example, however I think you miss to pass the arguments to the class constructor in the readonly function

  • Sean

    so I am trying to decorate a class, and it’s working fine, the only issue is that I am losing my original class’s constructor dependency injection and so of course everything is failing.

    so my original class is:

    @RefreshTheme
    export class App implements AfterContentInit {
    private m_commBroker:CommBroker;
    private m_styleService:StyleService;

    constructor(commBroker:CommBroker, styleService:StyleService) {
    this.m_styleService = styleService;
    this.m_commBroker = commBroker;

    so as you can see I am DI commBroker and styleService

    and my @RefreshTheme is:

    export function RefreshTheme(Target: TFunction): TFunction {
    var newConstructor = function () {
    Target.apply(this);
    Object.freeze(this);
    };

    newConstructor.prototype = Object.create(Target.prototype);
    newConstructor.prototype.constructor = Target;

    return newConstructor;

    }

    and as long as I don’t use DI in the original class, all is good… but I need to respect the original class constructor regardless of what it is (diff classes will of course have diff constructor signatures)…

    thanks for reading,

    regards

    Sean.

  • Pingback: Angular 2 – Getting Started | Stamplay Blog()

  • ahmed

    I am geting this error Error: Unexpected character “@” .
    my decorator is as given bellow.

    function mylog(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor) {

    var originalMethod = descriptor.value; // save a reference to the original method

    // NOTE: Do not use arrow syntax here. Use a function expression in

    // order to use the correct value of `this` in this method (see notes below)

    descriptor.value = function(…args: any[]) {

    console.log(“The method args are: ” + JSON.stringify(args)); // pre

    var result = originalMethod.apply(this, args); // run and store the result

    console.log(“The return value is: ” + result); // post

    return result; // return the result of the original method

    };

    return descriptor;

    }

    i have this decorator in individual file this file gets compile properly and when i want to use this decorator in another class in different file or same file like bellow

    @mylog

    retrieve (field, callback: (error: any, result: any) => void) {

    this._productRepository.retrieve(field, callback);

    }

    its giving me compiler error :Error: Unexpected character “@”

    for compilation i have gulp task as bellow

    gulp.task(“compile”, function () {

    gulp.src(‘src/config/*’)

    .pipe(gulp.dest(‘lib/config/’));

    return gulp

    .src(“src/**/*.ts”)

    .pipe(tsc({

    “module”: “commonjs”,

    “target”: “ES5”,

    “noImplicitAny”: false,

    “sourceMap”: false,

    “emitDecoratorMetadata”: true,

    “experimentalDecorators”: true,

    “logErrors”: true

    }))

    .pipe(gulp.dest(“lib”));

    });

  • Mahmoud Mohamed Mouneer

    If you declares property Person.name as a private static an error occured
    Error:(17, 2) TS2345:Argument of type ‘typeof Person’ is not assignable to parameter of type ‘Function’.
    Property ‘name’ is private in type ‘typeof Person’ but not in type ‘Function’.
    How can we deal with this problem?