TypeScript Classic Decorators
A decorator is a function the compiler calls with the class or member it is attached to. This lesson teaches the classic decorator form, which the experimentalDecorators flag enables.
A class decorator adds a member
The decorator writes a version string onto the prototype. Every instance reads the added member.
function versioned(target: Function): void {
target.prototype.version = '1.0';
}
@versioned
class SummaryCard {
title = 'Sales';
}
const card = new SummaryCard();
console.log(card.title);
console.log((card as unknown as { version: string }).version);A method decorator wraps a call
The decorator replaces the descriptor value with a function that logs, calls the original method, and returns its result.
function announce(target: object, key: string, descriptor: PropertyDescriptor): PropertyDescriptor {
const original = descriptor.value as (name: string) => string;
descriptor.value = function (this: unknown, name: string): string {
console.log('calling ' + key);
const result = original.call(this, name);
console.log('finished ' + key);
return result;
};
return descriptor;
}
class Greeter {
@announce
greet(name: string): string {
return 'Hello, ' + name;
}
}
console.log(new Greeter().greet('Maya'));A decorator factory takes an argument
The tag function returns the decorator, so each class can pass its own label text. The returned decorator stores the label on the prototype.
function tag(labelText: string) {
return function (target: Function): void {
target.prototype.label = labelText;
};
}
@tag('service')
class TaskRunner {
title = 'Runner';
}
const runner = new TaskRunner();
console.log((runner as unknown as { label: string }).label);Example
function role(labelText: string) {
return function (target: Function): void {
target.prototype.role = labelText;
};
}
function audit(target: object, key: string, descriptor: PropertyDescriptor): PropertyDescriptor {
const original = descriptor.value as (item: string) => string;
descriptor.value = function (this: unknown, item: string): string {
console.log('audit: ' + key);
return original.call(this, item);
};
return descriptor;
}
@role('editor')
class Publisher {
@audit
publish(item: string): string {
return 'published ' + item;
}
}
const publisher = new Publisher();
console.log(publisher.publish('draft'));
console.log((publisher as unknown as { role: string }).role);Key points
- A class decorator receives the constructor function of the class it decorates.
- A class decorator can add a member to every instance through the prototype.
- A method decorator receives the prototype, the method name, and a descriptor that holds the original method.
- A method decorator wraps a call by replacing the descriptor value with a new function.
- A decorator factory is a function that returns a decorator, so the decorator can take an argument.
- The next lesson teaches the current standard decorator form.
- The standard form needs no compiler flag.
In this example, the role factory adds a role member to the Publisher prototype, and the audit decorator wraps the publish method. The audit line prints before the result, and the added member reads back from the instance.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท TypeScript Playground
Exercise
What does a classic method decorator receive?