Learn About Decorators in JavaScript
Tags: node, javascript, webdev
Published: 2020-03-10 04:37:05


Find me on [medium](https://medium.com/@jsmanifest)

In web development, JavaScript is considered the most recommended language to build user interfaces that are highly complex, which can be coming from various needs especially coming from business requirements. And in this article we will be going over a useful pattern in JavaScript called _decorators_.

Decorators are objects you can use to _dynamically_ add additional functionality to another object, _without having to change the implementation of that object_. Just from understanding that definition we can most likely come to an agreement that they can become useful to our app code.

If you were like me, they might be a little confusing at first especially since in TypeScript the syntax was out of the ordinary. It doesn't quite _feel_ like JavaScript to be applying decorators to classes when applying them using the modern syntax (Currently upported in TypeScript and in babel plugins).

Here's an example of that in use:

```js
@filterMales // This is the decorator
class MyClass {
  constructor(children) {
    this.children = children
  }
}
```

Those of you who have never seen this type of code (specifically the `@filterMales` syntax) might feel a little frightened of decorators when realizing that this is applying a decorator. Decorators this way are just syntax sugar. Understanding and implementing a decorator might be _easier than you think_. If you've been developing in JavaScript for awhile, you've probably already implemented a decorator without even noticing it. They're simple but powerful.

We'll be taking a look at some examples of decorators in JavaScript and create our own decorator to see how it can be useful to our code.

## When is it a good time to use a Decorator?

Fortunately there are multiple ways a decorator can be useful to us.

### Adding Dynamic Behavior to Existing Objects

As previously mentioned, one scenario that can be very useful is when you need to _dynamically_ add additional logic to objects without having to deal with some alternatives (like subclassing or inheritance).

Remember this: decorators can _inject stuff_ into objects without the outside world even knowing _how_ they're going to do it.

For example, lets say we have a `Frog` class that will implement a method called `lick`. Frogs have teeth so we'll also randomly implement a `getTeeths` method to return the amount of teeths they have.

Here's what that may look like:

```js
function Frog(name) {
  this.name = name
}

Frog.prototype.getTeeths = function() {
  return 2
}

Frog.prototype.lick = function(target) {
  console.log(`I'm going lick you, ${target.name}. You better taste delicious`)
}

// Or with classes

class Frog {
  constructor(name) {
    this.name = name
  }

  getTeeths() {
    return 2
  }

  lick(target) {
    console.log(
      `I'm going lick you, ${target.name}. You better taste delicious`,
    )
  }
}
```

In reality there are different frogs, like a [toad](https://www.nwf.org/Educational-Resources/Wildlife-Guide/Amphibians/Toads) for example. A toad is still a frog _but_ a frog is _not_ a toad which means that there _must_ be some differentiating features between them that _must_ not be mixed.

Since a toad is a frog, we can build a `withToad` decorator that will decorate an instance of a frog if desired so that it can represent toads.

Remember, a decorator should only extend or add additional behavior to something but _not_ change its implementation.

Knowing this, the `withToad` decorator is actually quite simple:

```js
function withToad(frog) {
  frog.getTeeths = function() {
    return 0
  }
}

const mikeTheFrog = new Frog('mike')
withToad(mikeTheFrog)

console.log(mikeTheFrog.getTeeths())
```

Our decorator `withToad` re-implements `getTeeths` so that it returns `0` because toads _do not have teeth_. When we use this decorator we're essentially silently decorating (converting in this case) a frog to represent a frog that is a toad.

You _can_ achieve the same goal using subclassing with inheritance as shown below:

```js
function Toad(name) {
  Frog.call(this, name)

  this.getTeeths = function() {
    return 0
  }
}

const kellyTheToad = new Toad('kelly')

// or using classes

class Toad extends Frog {
  getTeeths() {
    return 0
  }
}

const kellyTheToad = new Toad('kelly')
```

The difference between the two approaches is that by using decorators you don't have to create classes for toads.

Our examples showed how decorators were used to manipulate a frog to be more aligned with the features of a toad.

Let's now look at a better example of how we can use decorators to extend functionality. This is where things begin to get a little interesting.

Lets pretend we're building an app that supports various custom predefined themes for users to style their control panel. We'll implement a `Theme` with the method `createStylesheet` to create a compatible stylesheet to work with, an `applyStyles` method to parse and apply this stylesheet to the DOM, allowing itself to call `applyStyle` to apply them to the DOM:

```js
function Theme() {}

Theme.prototype.createStylesheet = function() {
  return {
    header: {
      color: '#333',
      fontStyle: 'italic',
      fontFamily: 'Roboto, sans-serif',
    },
    background: {
      backgroundColor: '#fff',
    },
    button: {
      backgroundColor: '#fff',
      color: '#333',
    },
    color: '#fff',
  }
}

Theme.prototype.applyStylesheet = function(stylesheet) {
  const bodyElem = document.querySelector('body')
  const headerElem = document.getElementById('header')
  const buttonElems = document.querySelectorAll('button')
  this.applyStyles(bodyElem, stylesheet.background)
  this.applyStyles(headerElem, stylesheet.header)
  buttonElems.forEach((buttonElem) => {
    this.applyStyles(buttonElem, stylesheet.button)
  })
}

Theme.prototype.applyStyles = function(elem, styles) {
  for (let key in styles) {
    if (styles.hasOwnProperty(key)) {
      elem.style[key] = styles[key]
    }
  }
}
```

Things are looking great. We have now defined our `Theme` API and now we can create a stylesheet like so:

```js
const theme = new Theme()
const stylesheet = theme.createStylesheet()
```

Here is what `stylesheet` currently looks like:

```json
{
  "header": {
    "color": "#333",
    "fontStyle": "italic",
    "fontFamily": "Roboto, sans-serif"
  },
  "background": { "backgroundColor": "#fff" },
  "button": { "backgroundColor": "#fff", "color": "#333" },
  "color": "#fff"
}
```

And now we can use it like so, which will decorate our web page accordingly:

```js
theme.applyStylesheet(stylesheet)
```

![theme decorator in javascript apply default theme to web page](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/decorators-in-javascript/theme-decorator-javascript-apply-default-theme.jpg)

#### Continue with this in mind: Providing Open Opportunities to Support Plugin Development

How do we make `theme` return to us a custom theme when calling `createStylesheet` that we can work with to extend from instead of having to work with the default one?

This is where decorators can come in handy as it will allow us to return to us a different predefined default theme to work with.

We'll create a decorator that will help us apply a `blood` theme that will decorate `Theme` so that it generates us a default stylesheet that will represent the `blood` theme instead of the original.

We'll call this decorator `bloodTheme`:

```js
function bloodTheme(originalTheme) {
  const originalStylesheet = originalTheme.createStylesheet()
  originalTheme.createStylesheet = function() {
    return {
      name: 'blood',
      ...originalStylesheet,
      header: {
        ...originalStylesheet.header,
        color: '#fff',
        fontStyle: 'italic',
      },
      background: {
        ...originalStylesheet.background,
        color: '#fff',
        backgroundColor: '#C53719',
      },
      button: {
        ...originalStylesheet.button,
        backgroundColor: 'maroon',
        color: '#fff',
      },
      primary: '#C53719',
      secondary: 'maroon',
      textColor: '#fff',
    }
  }
}
```

Now all we have to do is decorate a `theme` with just one line:

```js
const theme = new Theme()
bloodTheme(theme) // Applying the decorator
const stylesheet = theme.createStylesheet()
console.log(stylesheet)
```

The theme now gives us a default `blood` stylesheet to work with:

```json
{
  "name": "blood",
  "header": {
    "color": "#fff",
    "fontStyle": "italic",
    "fontFamily": "Roboto, sans-serif"
  },
  "background": { "backgroundColor": "#C53719", "color": "#fff" },
  "button": { "backgroundColor": "maroon", "color": "#fff" },
  "color": "#fff",
  "primary": "#C53719",
  "secondary": "maroon",
  "textColor": "#fff"
}
```

As you can see the code/implementation of `theme` did not change. Applying the custom stylesheet did not change either:

```js
theme.applyStylesheet(stylesheet)
```

Now our web page will have the `blood` theme styles applied:

![theme-decorator-javascript-apply-blood-theme](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/decorators-in-javascript/theme-decorator-javascript-apply-blood-theme.jpg)

We can create as many themes as we want and apply them any time we wanted to. This means we left our code open for plugins like custom themes for example.

### Applying Temporary Behavior

Another good time to use decorators is when we're looking for ways to _temporarily_ apply behaviors to objects because we plan to remove it in the future.

For example, if christmas season is approaching we could easily create a _christmas stylesheet_ and apply it as a decorator. This is great because we can remove it easily from the code when christmas season is over. In the case of our previous example, all we needed to do to convert back to the original stylesheet was just remove the `bloodTheme(theme)` line.

### Subclassing/Inheritance

Another good use case for using decorators is when creating subclasses start to become unmanageable when our code is becoming large. However, this problem is not that much of an issue in JavaScript as opposed to static languages like Java--unless you're heavily using class inheritance implementations in JavaScript.

### Debug Modes

Another useful use case is creating a _debug mode_ decorator where when applied it will log every thing that happens to the console. For example here is a `debugTheme` decorator that will be useful to us in development mode:

```js
function debugTheme(originalTheme) {
  const stylesheet = originalTheme.createStylesheet()
  console.log(
    '%cStylesheet created:',
    'color:green;font-weight:bold;',
    stylesheet,
  )
  if (!stylesheet.primary) {
    console.warn(
      'A stylesheet was created without a primary theme color. There may be layout glitches.',
    )
  }
}

const theme = new Theme()
bloodTheme(theme)
if (process.env.NODE_ENV === 'development') debugTheme(theme)
```

Our console now gives useful information when we're running our app in `development` mode:

![debug mode theme decorator javascript console log messages](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/decorators-in-javascript/theme-decorator-javascript-apply-debug-theme.jpg)

Find me on [medium](https://medium.com/@jsmanifest)