← Back to TypeScript Course | Chapter 10: Decorators | Lesson 7 of 7

Decorator Composition

Decorator composition means applying multiple decorators to the same declaration. Their order matters because decorator expressions are evaluated from top to bottom, while the decorators themselves are applied from bottom to top.

Applying Multiple Decorators

You can stack several decorators on the same class member, one per line, and TypeScript applies each of them to progressively wrap or transform the target in sequence.

Example: Applying Multiple Decorators

typescript
function a(target: any, ctx: ClassMethodDecoratorContext) { console.log("a applied"); }
function b(target: any, ctx: ClassMethodDecoratorContext) { console.log("b applied"); }
class Example {
  @a
  @b
  run() {}
}
new Example();

Evaluation and Application Order

Decorator factories evaluate top-to-bottom in the order they're written, but the decorators they return are actually applied bottom-to-top — understanding this two-phase order is essential to predicting the final behavior.

Example: Evaluation and Application Order

typescript
function factory(name: string) {
  console.log("Evaluating factory", name);
  return function (target: any, ctx: ClassMethodDecoratorContext) {
    console.log("Applying decorator", name);
  };
}
class Example {
  @factory("top")
  @factory("bottom")
  run() {}
}
new Example();

Composing Method Wrappers

Composing decorators that each wrap a method (like one for logging and one for timing) lets you layer independent method behaviors without merging their logic into a single, harder-to-maintain decorator.

Example: Composing Method Wrappers

typescript
function withLogging(target: any, ctx: ClassMethodDecoratorContext) {
  return function (this: any, ...args: any[]) {
    console.log("log:", String(ctx.name));
    return target.call(this, ...args);
  };
}
function withTiming(target: any, ctx: ClassMethodDecoratorContext) {
  return function (this: any, ...args: any[]) {
    console.log("timing started");
    return target.call(this, ...args);
  };
}
class Service {
  @withLogging
  @withTiming
  run() {
    return "done";
  }
}
new Service().run();

Combining Different Behaviors

Different decorators can be combined to add unrelated capabilities to the same class member — for example pairing a validation decorator with a logging decorator — as long as each one still returns a compatible descriptor.

Example: Combining Different Behaviors

typescript
function validate(target: any, ctx: ClassMethodDecoratorContext) {
  return function (this: any, ...args: any[]) {
    console.log("validating");
    return target.call(this, ...args);
  };
}
function log(target: any, ctx: ClassMethodDecoratorContext) {
  return function (this: any, ...args: any[]) {
    console.log("logging");
    return target.call(this, ...args);
  };
}
class Form {
  @validate
  @log
  submit() {
    return "submitted";
  }
}
new Form().submit();

Order Changes Results

Because application order runs bottom-to-top, reordering two decorators on the same member can change which one sees the other's modified output first, so swapping their order can genuinely change the result.

Example: Order Changes Results

typescript
function addOne(target: any, ctx: ClassMethodDecoratorContext) {
  return function (this: any) {
    return target.call(this) + 1;
  };
}
function double(target: any, ctx: ClassMethodDecoratorContext) {
  return function (this: any) {
    return target.call(this) * 2;
  };
}
class MathOp {
  @double
  @addOne
  compute() {
    return 5;
  }
}
console.log(new MathOp().compute());
🔒

Chapter Quiz — Complete all 7 topics to unlock

0/7 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.