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@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
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
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
Dennis O'Keeffe
Melbourne, Australia
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