๐ŸŽ“ A1Learner
๐Ÿ”
0 of 37 lessons completed

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?