TypeScript Decorator Practice

· 2 min read · 303 Words · -Views -Comments

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.

decorator illustration

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:

  1. A decorator is just a function.
  2. 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.

References

Authors
Developer, digital product enthusiast, tinkerer, sharer, open source lover