Back to home

TypeScript Decorators: An Introduction To Metadata And Annotation

Published: Jun 19, 2023

Last updated: Jun 19, 2023

This post will is 5 of 20 for my series on intermediate-to-advance TypeScript tips.

All tips can be run on the TypeScript Playground.

Introduction

Decorators, a proposed feature for JavaScript, are already available as an experimental feature in TypeScript. 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.

{ "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.

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:

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. 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.

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:

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.

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.

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:

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. 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.

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:

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

You can see this in action here.

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

Photo credit: adrienconverse

Personal image

Dennis O'Keeffe

@dennisokeeffe92
  • Melbourne, Australia

Hi, I am a professional Software Engineer. Formerly of Culture Amp, UsabilityHub, Present Company and NightGuru.
I am currently working on Visibuild.

1,200+ PEOPLE ALREADY JOINED ❤️️

Get fresh posts + news direct to your inbox.

No spam. We only send you relevant content.

TypeScript Decorators: An Introduction To Metadata And Annotation

Introduction

Share this post