TypeScript Decorator Practice
Decorators are still an ES stage-2 proposal. In TypeScript/Babel projects we can opt in to experimental features, and frameworks like Angular make heavy use of them. I’d used decorators in Angular, but hadn’t explored them deeply—time to change that.
What’s a Decorator?
Decorators “wrap” classes/members to add behavior without changing the original interface. Consumers shouldn’t notice a difference.
Types:
- Class decorators
- Method decorators
- Accessor decorators
- Property decorators
- Parameter decorators
- Metadata decorators
Key points:
- A decorator is just a function.
- In TS they only apply to classes and class elements—not enums, constants, or standalone functions.
Examples
Property decorator — tweak a property value:
function modifyProp(target: any, propertyKey: string) {
target[propertyKey] = Math.random().toString();
}
Class decorator — modify every property:
function modifyProps(prefix: string) {
return (constructor: any) => {
Object.keys(constructor).forEach(
(key) => (constructor[key] = prefix + key)
);
return constructor;
};
}
Enabling Decorators
Using TypeScript + Babel:
yarn add @babel/plugin-proposal-decorators -D
tsconfig.json
:
"experimentalDecorators": true,
"emitDecoratorMetadata": true
(Enable emitDecoratorMetadata
if you rely on reflect-metadata
.)
babel.config.js
(put the plugin first):
plugins: [
['@babel/plugin-proposal-decorators', { legacy: true }]
]
Babel transpiles decorators to regular functions, so browser compatibility doesn’t worsen.
Practical Example
Our Redux action types were defined like this:
import { createActionPrefix } from 'redux-actions-helper';
const prefixCreator = createActionPrefix('USER');
export default class UserActionTypes {
static INIT_USER = prefixCreator('INIT_USER');
static SET_BUSINESS_UNIT = prefixCreator('SET_BUSINESS_UNIT');
}
Refactor with a class decorator:
import { createActionPrefix } from 'redux-actions-helper';
@actionTypes('USER')
export default class UserActionTypes {
static INIT_USER = null;
static SET_BUSINESS_UNIT = null;
}
export function actionTypes(prefix: string) {
return (constructor: any) => {
const factory = createActionPrefix(prefix);
Object.keys(constructor).forEach((key) => {
constructor[key] = factory(key);
});
return constructor;
};
}
Cleaner and non-invasive.
Final Thoughts
Decorators are just functions, but they encourage a design pattern: extend behavior without cluttering the original class. Use them thoughtfully.