# TypeScript Decorators: An Introduction To Metadata And Annotation

> Decorators, a proposed feature for JavaScript, are already available as an experimental feature in TypeScript. See an example of decorators in action in this post exploring the topic.

- Canonical: https://dennisokeeffe.com/blog/2023-06-19-typescript-decorators-an-introduction-to-metadata-and-annotation
- Published: 2023-06-19
- Updated: 2023-06-19
- Tags: typescript, decorators, metadata, annotation

This post will is 5 of 20 for my [series on intermediate-to-advance TypeScript tips](https://blog.dennisokeeffe.com/blog/series/20-days-of-type-script).

All tips can be run on the [TypeScript Playground](https://www.typescriptlang.org/play).

## Introduction

Decorators, a proposed feature for JavaScript, are already available as an [experimental feature in TypeScript](https://www.typescriptlang.org/docs/handbook/decorators.html). Decorators provide a way to add both annotations and a meta-programming syntax for class declarations and members. They are a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form `@expression`, where `expression` must evaluate to a function that will be called at runtime with information about the decorated declaration.

## Setting Up

First, to use decorators in TypeScript, you need to enable the `experimentalDecorators` compiler option in your `tsconfig.json` file.

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

## Class Decorators

Class decorators are applied to the constructor of the class and can be used to observe, modify, or replace a class definition.

```typescript
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}
```

In this example, `@sealed` is a class decorator. It prevents new properties from being added to the class and marks existing ones as non-configurable.

To see this in action, we can attempt to add a new property to the `Greeter` class:

```typescript
const greeter = new Greeter("world");
console.assert(Object.isSealed(greeter), "Greeter instance should be sealed");
```

This will throw an error because the `Greeter` class has been sealed, preventing new properties from being added.

You can see this in action [here](https://www.typescriptlang.org/play?experimentalDecorators=true#code/GYVwdgxgLglg9mABAZwKYEMA2qAmAKCBZKAJxGjhIC5EAxcaeMASkQG8AoRRAeQCMAVqmgA6NFgJFS5KJWYBuLr0HCoYjJklhiZCiREAHEnFlQAngdQKOAXw4cAAuOw4OETOmTJEAcRKpUKFQSdiUAc39AmDAwmh1osMVuQm1pPTwAW1QvdDDUONIE1k5ubigACxhkEQiA2BjEAF5ELJy8pMQ7blrAvGKlbn8oEBIkACIACVRMTDgAGkQxxABqRAqqmsj6xKU7OzcpRB6gkOawVAB3Xy3gvDGLykwcMesU5DhsEU80Eig8fiEoiqAGUNLg8MdgswFgByPx1YKIaLEdCQVAocpwEBPRB8dHOXAw6xAA). Please note, you will need to open the actual console to see the console assertion failure.

## Method Decorators

Method decorators are applied to the property descriptor of the method and can be used to observe, modify, or replace a method definition.

```typescript
function enumerable(value: boolean) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    descriptor.enumerable = value;
  };
}

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }

  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting;
  }
}
```

In this example, `@enumerable(false)` is a method decorator on the `greet` method. It makes the method non-enumerable.

To see it in use:

```typescript
let greeter = new Greeter("world");
for (let prop in greeter) {
  console.log(prop); // This will not log 'greet'
}
```

In this example, iterating over the properties of `greeter` will not log the `greet` method because it has been made non-enumerable. However, setting `enumerable` to `true` will log the `greet` method.

Play around with it [here](https://www.typescriptlang.org/play?experimentalDecorators=true#code/GYVwdgxgLglg9mABAUzCAtsgTgQwEYA2yAFAG44EjIBcieccROYAlIgN4BQiiWyUILElCRYCRMW49EUHFgDm-WswCeAGik8ADljhbsUFQGlkK2gGcoWGGHkbpiACbJzEa1qhwstAAq79WIYAIi5uMB5eUmxcDs6u7p5YAHSoGNj4RIgAvIjklMgA3FIAvkXFnJwQBDjm5ogA4nz82BxS8k2wthZWNvJFPBAIllgg0F7EmLU4it3WttGaMgAWMOZJ7cj8vdmIk+bThSUVPAACqZi4hCTAFObILG0dxAsOfAJCiABEABLIBARwNRfRAAamWq3WHV6-UQ5XKnCIUEQG2aWB2YGQAHcGh1sMRPpivARHJ8WEVgF4JIjEDo9IgbMjcVgXohBmBzIxkEkAfJiLStGTEAB6IWIAAqKzqmJg-0QYDgSJ5iAA5CioMrOOUgA).

## Property Decorators

Property decorators are applied to a property declaration. They cannot be used in declaration files or any other ambient context. Decorators can be used to modify the behaviour of a property.

```typescript
function nonconfigurable(target: any, propertyKey: string) {
  let value = target[propertyKey];

  const getter = function () {
    return value;
  };

  const setter = function (newVal: any) {
    console.assert(false, "Error: This property is non-configurable");
    value = newVal;
  };

  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: false,
  });
}

class Greeter {
  @nonconfigurable
  greeting: string = "Hello, world";
}
```

In this example, the `@nonconfigurable` decorator is applied to the `greeting` property. This decorator does not allow the property to be reassigned, and trying to do so will log an error message in the console.

Here is how it behaves at runtime:

```typescript
let greeter = new Greeter();
greeter.greeting = "Hello, TypeScript"; // Will log an error: This property is non-configurable
```

This approach ensures that the property remains non-configurable, highlighting the capabilities of decorators in enforcing certain constraints on class properties.

Play around with it [here](https://www.typescriptlang.org/play?experimentalDecorators=true#code/GYVwdgxgLglg9mABGBEHBgcxAJwIYBGANgKYAUUeOmJUAXInmAJ4A0iADjnByTlMwDSJZgwDOUHDDCYAlIgDeAKESJSURADc8RECUQBeRJWq0A2lx58Bw5gF0A3EpWI0YCYhpQofQ4lCQsAhk8sqqqji0uEjauiROqgC+Ti5uHmK0Pjh+AdDwYGRgJADuAGo6DEzMoS6qaXCkAHR4Yhn8ZMA6GewARACiONw4DAAqABYwYpzcvPzMiJPICAC0bhjY+MQkPbIJ4bF6fkVlOnvJzqoA8gQAViTQjQAmJBhFAAoz1swUVF7slrMbCJ2GFwl4GF4sqxaogMvRYZk+NDwogSGAQABbPiEUgMSR6ZHhNZYXA4kgMTpEDIuRK7JSJZwQIgtKYAcUitF8oIAAihIOgSZtSC5MBzYDJxJJpJg-D0ABIkIhEODsYpwHBER49JwMpTqTxi3xGY6IdkkTk4EJOUXmrKNG20aWyhVKlWIEbMXgAZQgUg4UG1iAA9EHEAB1GBKtRwGVMVGDdWjCZTAFfBZTPmrAUbMlKIA). Please note, since we are asserting the runtime error, you will need to open the actual browser console to see the error.

## Parameter Decorators

Parameter decorators are applied to the function for a class constructor or method declaration.

```typescript
function required(
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) {
  console.log(
    `Required parameter in ${propertyKey.toString()} at position ${parameterIndex}`
  );
}

class Greeter {
  greet(@required name: string) {
    return "Hello " + name;
  }
}
```

In this example, the `@required` decorator logs a message about required parameters each time the `greet` method is called.

To see it in action:

```typescript
let greeter = new Greeter();
greeter.greet("world"); // Will log: Required parameter in greet at position 0
```

You can see this in action [here](https://www.typescriptlang.org/play?experimentalDecorators=true#code/GYVwdgxgLglg9mABAJwKYEcQzQEwBRQCGyA5qlAFyIDyARgFarQA0iADsnG6slAJ4BpVHyoBnKMhhgSiAD6JRfALa04AG1ZtihJeR4BJMDlQAPKmBAqeASkQBvAFCJEEBKPWoAdGrgk8AAwAlDCxcdm1dKB5EKUQAEjsOLh5+IT5PKDgAZQkpP2sAX0RCKHY4URhYBHjEiL1kQ2MTAv9rAG4HAocHCDVCUVFEAHE0evsnRBJRqDwAATRMbFQcRDAdVDFc6VtHZ2c0KBBkJAAiAAlUNR9EE8QAalX1jucuroc1cknp6IBeVdQAO7Db7IPDtHpuDzeXx4KaoeqeOHkPAnAFwZBqHAnaztRAAejxiAA6jArogfCQqMFFmEtMh1lFkDEkEjSiUyhUqkgAAwOIA).

## Summary

TypeScript decorators provide a powerful and expressive way to add metadata and annotations to your code. By enabling the `experimentalDecorators` option, you can experiment with class, method, property, and parameter decorators. This hands-on approach shows how decorators can be used to shape the behavior and structure of your TypeScript classes and members.

## Resources and further reading

- [TypeScript: decorators](https://www.typescriptlang.org/docs/handbook/decorators.html)
- [Class decorators example](https://www.typescriptlang.org/play?experimentalDecorators=true#code/GYVwdgxgLglg9mABAZwKYEMA2qAmAKCBZKAJxGjhIC5EAxcaeMASkQG8AoRRAeQCMAVqmgA6NFgJFS5KJWYBuLr0HCoYjJklhiZCiREAHEnFlQAngdQKOAXw4cAAuOw4OETOmTJEAcRKpUKFQSdiUAc39AmDAwmh1osMVuQm1pPTwAW1QvdDDUONIE1k5ubigACxhkEQiA2BjEAF5ELJy8pMQ7blrAvGKlbn8oEBIkACIACVRMTDgAGkQxxABqRAqqmsj6xKU7OzcpRB6gkOawVAB3Xy3gvDGLykwcMesU5DhsEU80Eig8fiEoiqAGUNLg8MdgswFgByPx1YKIaLEdCQVAocpwEBPRB8dHOXAw6xAA)
- [Method decorators example](https://www.typescriptlang.org/play?experimentalDecorators=true#code/GYVwdgxgLglg9mABAUzCAtsgTgQwEYA2yAFAG44EjIBcieccROYAlIgN4BQiiWyUILElCRYCRMW49EUHFgDm-WswCeAGik8ADljhbsUFQGlkK2gGcoWGGHkbpiACbJzEa1qhwstAAq79WIYAIi5uMB5eUmxcDs6u7p5YAHSoGNj4RIgAvIjklMgA3FIAvkXFnJwQBDjm5ogA4nz82BxS8k2wthZWNvJFPBAIllgg0F7EmLU4it3WttGaMgAWMOZJ7cj8vdmIk+bThSUVPAACqZi4hCTAFObILG0dxAsOfAJCiABEABLIBARwNRfRAAamWq3WHV6-UQ5XKnCIUEQG2aWB2YGQAHcGh1sMRPpivARHJ8WEVgF4JIjEDo9IgbMjcVgXohBmBzIxkEkAfJiLStGTEAB6IWIAAqKzqmJg-0QYDgSJ5iAA5CioMrOOUgA)
- [Property decorators example](https://www.typescriptlang.org/play?experimentalDecorators=true#code/GYVwdgxgLglg9mABGBEHBgcxAJwIYBGANgKYAUUeOmJUAXInmAJ4A0iADjnByTlMwDSJZgwDOUHDDCYAlIgDeAKESJSURADc8RECUQBeRJWq0A2lx58Bw5gF0A3EpWI0YCYhpQofQ4lCQsAhk8sqqqji0uEjauiROqgC+Ti5uHmK0Pjh+AdDwYGRgJADuAGo6DEzMoS6qaXCkAHR4Yhn8ZMA6GewARACiONw4DAAqABYwYpzcvPzMiJPICAC0bhjY+MQkPbIJ4bF6fkVlOnvJzqoA8gQAViTQjQAmJBhFAAoz1swUVF7slrMbCJ2GFwl4GF4sqxaogMvRYZk+NDwogSGAQABbPiEUgMSR6ZHhNZYXA4kgMTpEDIuRK7JSJZwQIgtKYAcUitF8oIAAihIOgSZtSC5MBzYDJxJJpJg-D0ABIkIhEODsYpwHBER49JwMpTqTxi3xGY6IdkkTk4EJOUXmrKNG20aWyhVKlWIEbMXgAZQgUg4UG1iAA9EHEAB1GBKtRwGVMVGDdWjCZTAFfBZTPmrAUbMlKIA)
- [Parameter decorators example](https://www.typescriptlang.org/play?experimentalDecorators=true#code/GYVwdgxgLglg9mABAJwKYEcQzQEwBRQCGyA5qlAFyIDyARgFarQA0iADsnG6slAJ4BpVHyoBnKMhhgSiAD6JRfALa04AG1ZtihJeR4BJMDlQAPKmBAqeASkQBvAFCJEEBKPWoAdGrgk8AAwAlDCxcdm1dKB5EKUQAEjsOLh5+IT5PKDgAZQkpP2sAX0RCKHY4URhYBHjEiL1kQ2MTAv9rAG4HAocHCDVCUVFEAHE0evsnRBJRqDwAATRMbFQcRDAdVDFc6VtHZ2c0KBBkJAAiAAlUNR9EE8QAalX1jucuroc1cknp6IBeVdQAO7Db7IPDtHpuDzeXx4KaoeqeOHkPAnAFwZBqHAnaztRAAejxiAA6jArogfCQqMFFmEtMh1lFkDEkEjSiUyhUqkgAAwOIA)

_Photo credit: [`adrienconverse`](https://unsplash.com/@adrienconverse)_
