Decorator Composition
In this page:
@Decorator1
@Decorator2
class ClassName {}
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
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
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
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
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
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());
- Assuming stacked decorators apply top-to-bottom, when they are applied bottom-to-top (only factories evaluate top-to-bottom).
- Writing decorators that depend on each other's order without noticing that swapping lines changes the result.
- Having two wrapping decorators that both call the original method incorrectly, so the inner one gets skipped.
- Decorators attach extra behavior to classes, methods, properties, and parameters using the
@syntax. - Decorator factories return decorators so they can accept configuration arguments.
- Multiple decorators can be composed on one target, and they are applied in a defined order.
Chapter Quiz — Complete all 7 topics to unlock
0/7 topics done
Complete these topics first: