📓
Design Patterns & Principles
  • Recipes: Design Patterns And Principles
  • Design Patterns
    • Singleton Pattern
    • Module Pattern
    • Observer Pattern
    • Decorator Pattern
    • Factory Method Pattern
    • Builder Pattern
    • Adapter Pattern
    • Bridge Pattern
    • Composite Pattern
    • Facade Pattern
    • Flyweight Pattern
    • Proxy Pattern
    • Chain of Responsibility
    • Command Pattern
    • Iterator Pattern
    • Mediator Pattern
    • Memento Pattern
    • Visitor Pattern
    • Strategy Pattern
    • State Pattern
    • Template Method Pattern
    • Prototype Pattern
  • Software Principles
    • SOLID Principle
    • DRY Principle
    • Software Architecture Fundamentals
      • Architecture Style
        • Service-Oriented Architecture (SOA)
Powered by GitBook
On this page
  • The basics of decorators
  • Decorators in TypeScript
  • Class decorator
  • Method decorators
  • Property decorators
  • Accessor decorators
  • Parameter decorators
  • Summary
  • Resources

Was this helpful?

  1. Design Patterns

Decorator Pattern

When we are cold, we put on a shirt. If that’s not enough, we put a coat on top. Decorators work similarly. In this article, we discuss the principles of the decorator design pattern and talk about th

PreviousObserver PatternNextFactory Method Pattern

Last updated 4 years ago

Was this helpful?

Winter Be Like

The basics of decorators

The Decorator Pattern is one of the well-known design patterns . The fundamental purpose of decorators is to attach new features and behaviors to already defined classes and methods. If we consider the above to be the core of decorators, it is easy to implement our decorator:

class Yogurt {
  constructor(flavor) {
    this.flavor = flavor;
  }
}
 
function frozen(object) {
  return Object.freeze(object);
}
 
const yogurt = frozen(
  new Yogurt('strawberry')
);

Furthermore, our decorators can be stacked up on top of each other. It might sometimes be tricky, though.

function eatable(object) {
  object.eatable = true;
  return object;
}
const yogurt = eatable(
  frozen(
    new Yogurt('strawberry')
  )
);

The above example might not work as you expect, because when we attempt to make the yogurt to be eatable, it is frozen already. We need to revert the order of our decorators.

const yogurt = frozen(
  eatable(
    new Yogurt('strawberry')
  )
);

We can imagine that this pattern would come in handy quite often. Unfortunately, the above syntax is not very elegant. Thankfully, the efforts have been made to standardize decorators both in JavaScript and TypeScript.

Decorators in TypeScript

First, let’s look into decorators in TypeScript, because they are a built-in feature. The only thing we need to do is to turn on the experimentalDecorators flag in our tsconfig.json file.

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

As you can see above, the decorators are still an experimental feature in TypeScript, and they might change in the future.

Class decorator

We can declare a class decorator by adding it just before the class declaration. Its argument is the constructor of our class.

Let’s define a decorator that logs out the name of every created instance.

function log(constructor: any) {
  console.log(`New ${constructor.name} created!`);
}
@log
class Yogurt {
  public flavor: string;
  constructor(flavor: string) {
    this.flavor = flavor;
  }
}

The issue with the code is that ourconsole.log is called only once when our class is initialized. Thankfully, we can replace the original constructor by returning a value from our class decorator.

function log(constructor: any) {
  return function(...args: any[]) {
    console.log(`New ${constructor.name} created!`);
    return new constructor(...args);
  } as typeof constructor;
}

In the above code, we log out the name of the class every time we call the constructor.

new Yogurt('strawberry');

new Yogurt('cherry');

Method decorators

We add a method decorator just before the declaration of a method. We can use it to track, alter, or replace a method definition.

This is a great moment to use the decorator factory approach. It involves creating a function that returns a decorator. We can find it very useful when wanting to pass some additional properties to the decorator.

The method decorator has three arguments:

  • the constructor function of the class (for a static property), or the prototype of the class (for instance property),

  • name of the property,

  • the property descriptor.

Let’s create a method decorator that removes provided properties from the return value. We might find it useful, for example, to strip out a hash of the password of a user, when returning his data.

function excludeProperties(propertiesToExclude: string[]) {
  return (target: any, propertyName: string, descriptor: PropertyDescriptor) => {
    const originalFunction = descriptor.value;
 
    descriptor.value = async function(...args: any[]) {
      const originalResult = await originalFunction.apply(this, args);
      propertiesToExclude.forEach(propertyName => {
        delete originalResult[propertyName];
      });
      return originalResult;
    };
  }
}

In the code above, we save the reference to the original function using the descriptor.value property. We use the async/await syntax because we want to use the excludeProperties function to decorate a method that returns a promise.

import userModel from './user.model';
import UserNotFoundException from './UserNotFoundException';
import excludeProperties from './excludeProperties';
 
class UserService {
  private user = userModel;
 
  @excludeProperties(['password'])
  private getUser = async (userId: string) => {
    const user = await this.user.findById(userId);
    if (user) {
      return user;
    }
    throw new UserNotFoundException(userId);
  }
}

Thanks to our decorator, the getUserById function returns a user but removes the password from the return value.

Property decorators

We can add property decorators just before declaring a property. It has two arguments:

  • the constructor function of the class (for a static property), or the prototype of the class (for instance property),

  • name of the property.

function log(target:any, propertyName: string) {
  console.log(`The property ${propertyName} added to the class`);
}
 
class Yogurt {
  @log
  public flavor: string;
  constructor(flavor: string) {
    this.flavor = flavor;
  }
}

Accessor decorators

The accessor decorators are similar to the method decorators. The catch to them is that we can’t decorate both get and set accessors of a single property. We need to apply a decorator to the first accessor in our class.

function enumerable(isEnumerable: boolean) {
  return function (target: any, propertyName: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = isEnumerable;
  };
}
class TranslationsService {
  private currentLanguage: string;
  previousLanguages: string [] = [];
 
  @enumerable(false)
  get language() {
    return this.currentLanguage;
  }
  set language(newLanguage: string) {
    if (this.currentLanguage) {
      this.previousLanguages.push(this.currentLanguage)
    }
    this.currentLanguage = newLanguage;
  }
}

Parameter decorators

We can add a parameter decorator before the declaration of a parameter inside of a function. It has three arguments:

  • the constructor function of the class (for a static property), or the prototype of the class (for instance property),

  • name of the property,

  • the index of the parameter

function log(target: any, parameterName: string, parameterIndex: number) {
  console.log(`Added a parameter number ${parameterIndex}`);
}
 
class Yogurt {
  public flavor: string;
  constructor(
    @log flavor: string
  ) {
    this.flavor = flavor;
  }
}

Summary

The decorator pattern proves to be useful when we want to assign some additional behavior.

In this article, we’ve gone through all of the types of decorators that we can use.

Resources

Thanks to the frozen function, we now can get an object which properties can’t be changed. The above happens thanks to the usage of the function. We’ve made it easily reusable, and therefore, we can attach it to an object of any class.

The code above might look a bit like using Higher-Order Components with React. You could actually and they are sometimes .

The Decorator is a special syntax that provides us with a way to attach additional logic to classes, methods, accessors, parameters, and properties. They are used heavily in frameworks like and .

As you can see above, the arguments do not include property descriptors. We can’t really do much with them alone, but you might find them useful when exploring the .

Or at least the documentation states the above is the case. There seems to be some .

Similarly to the property decorators, the parameter decorators can just be used to observe that a parameter was declared and do some side-effects. You might find it useful with the library.

They are an elegant and reusable solution and allow us to delegate some of the logic from outside of our classes, making the easier to keep.

Object.freeze
treat them as hooks
seen as such
Angular
NestJS
reflect-metadata library
confusion regarding the second argument
reflect-metadata
Single Responsibility Principle
described by the “gang of four”
JavaScript design patterns #4. Decorators and their implementation in TypeScriptMarcin Wanago Blog - JavaScript, both frontend and backend
Logo