To start with, Annotations are an example of declarative programming in action. Rather than write out the procedural code to make something happen everywhere you need it, you write the code once and then declare (through an annotation) where you want that code to be run. You may not have to even write a lines of the code: It might be part of the framework code runs inside of. It's a great way to handle crosscutting concerns (things that are needed in many places in the application but not in every place). It sounds like the way that attributes are used in the Microsoft .NET Framework, (think of the ASP.NET Authorize action filter, for example).

With the introduction of Classes in TypeScript and ES6, there now exist certain scenarios that require additional features to support annotating or modifying classes and class members. Decorators provide a way to add both meta-programming syntax and a annotations for class declarations and members.

We can simply say Decorators are functions that modify a class, property, method, or method parameter.
To enable support for decorators, you must first enable the experimentalDecorators compiler option in your tsconfig.json.

 {
   "compilerOptions": 
   {
     "target": "ES5",
       "experimentalDecorators": true
   }
}

A Method Decorator is declared just before a method declaration using following syntax.
“@” symbol followed by a functionName like

@expression

Where expression must evaluate to a function which will be called at runtime with information about the decorated declaration.

The general syntax of defining the method decorator is as below.

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

Now that we know how the signature of a decorator looks like we can implement some of them. We are going to start by implementing a method decorator. Let’s create a method decorator named log.

class Test 
{
    @log
    multiply(n: number) 
    {
        return n * 2;
    }
 }

Before we can actually use @log we need to declare the method decorator somewhere in our application. Let’s take a look to the log method decorator implementation.

function log(target: Function, key: string, value: any) 
{
    return 
    {
        value: function (...args: any[]) 
        {
            var a = args.map(a => JSON.stringify(a)).join();
            var result = value.value.apply(this, args);
            var r = JSON.stringify(result);
            return result;
        }
    };
}

The method decorator is applicable as a Property Descriptor to the method, and it can be used to observe, modify, or replace a method definition. A method decorator is nothing but a function that accepts 3 arguments as below

  • An object on which the method defination is defined
  • The key for the property (a symbol or string name)
  • Property descriptor(The function returns a property descriptor otherwise, returning undefined is equivalent to returning the descriptor passed in as argument)

So now,

 - target : We will get the object of Test class.
 - key : Name of the method on which decorator applied i.e is multiply
 - value :Will get the property descriptor of a function (Object.getOwnPropertyDescriptor() function.)

If we return to the log decorator implementation we will be able to understand much better what happens when it is invoked.

function log(target: Function, key: string, value: any) 
{
         return {
            value: function (...args: any[]) 
            {
    
                // convert list of multiplyarguments to string
                var a = args.map(a => JSON.stringify(a)).join();
    
                // invoke multiply() and get its return value
                var result = value.value.apply(this, args);
    
                // convert result to string
                var r = JSON.stringify(result);
        
                // return the result of invoking multiply
                return result;
            }
    }

}

After decorating the multiply method it will continue to work as usually but it will also execute the extra logging functionality added by the log the decorator.

var c = new Test();
var r = c.multiply(23); //  "Call: multiply(23) => 46"
console.log(r); 

So following example might be the case where you can find method decorator more useful.

 class CRUD 
{
    get() { }
    post() { }
 
    @admin
    delete() 
		{
  		// only admin can invoke this
		}

} 

In the above example, @admin which is a decorator needs to implement it somewhere in application which 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.

References:
1: https://www.typescriptlang.org/docs/handbook/decorators.html