Decorators do not work as you might expect 🤔
Tags: typescript, webdev, javascript, angular
Published: 2019-07-16 10:05:14

While working on a library called [ngx-template-streams](https://github.com/typebytes/ngx-template-streams), which in a nutshell allows you to work with events as streams in Angular templates, I have discovered that decorators are not instance-based but rather class-based. I was not aware of this behavior and thought that decorators get applied per class instance. In this blog post, we'll take a closer look at decorators and explore why they behave this way and how we can create instance-based decorators.

_Cover photo by Garett Mizunaka on Unsplash_

# Quick Recap on Decorators

Decorators are great. They allow us to add annotations and a meta-programming syntax for class declarations and members, including properties, accessors, parameters, and methods. In other words, we can use decorators to attach additional responsibility to an object without modifying any other object. Therefore, they are great to compose pieces of functionality in a declarative fashion. That means the [decorator design pattern](https://en.wikipedia.org/wiki/Decorator_pattern) is designed in a way that multiple decorators can be stacked on top of each other, each adding new functionality. 

Also, many people consider decorators as a **flexible alternative to subclassing**. While subclassing adds behavior at compile-time, and therefore affecting all instances, decorators add behavior to individual objects at **runtime**.

So decorators have gained much popularity, and quite frankly for a reason. They make our code easier to read, test, and maintain. Thus, some of the leading open source projects have adopted the decorator design pattern, including [Angular](https://github.com/angular/angular), [Inversify](https://github.com/inversify) or [Nest](https://github.com/nestjs/nest).

Ok, so what's a decorator?

[Idan Dardikman](https://medium.com/@idandrd) summarizes this question wonderfully:

> Decorators are just a clean syntax for wrapping a piece of code with a function

TypeScript has **experimental** support for decorators. However, there is an [ECMAScript decorator proposal](https://github.com/tc39/proposal-decorators) that has reached stage 2 (draft), so they could eventually land in vanilla JS.

As mentioned earlier, there are different types of decorators. For example, we could attach a decorator to a class:

```ts
@Component()
class HeroComponent {}
}
```

The `@Component()` is an excellent example for a class decorator, and it's one of the core building blocks in [Angular](https://angular.io/). It attaches additional metadata to the class.

Most likely you'll also encounter some **property**, **method** or **parameter** decorators along the way:

```ts
@Component()
class HeroComponent {
  @Input() name: string;
  
  constructor(@Inject(TOKEN) someDependency: number) {}
  
  @deprecated
  greet() {
    console.log('Hello there!');      
  }
}
```

So decorators are quite universal, expressive, and powerful. Now, this blog post is not about explaining decorators in all their details. In this post, we implement a property decorator to explore their behavior, but we won't look at the implementation of other types of decorators. If you want to learn more about decorators in general, I highly recommend the [official documentation](https://www.typescriptlang.org/docs/handbook/decorators.html), this [gentle introduction](https://medium.com/iqoqo-engineering/understand-typescript-decorators-in-5-minutes-26ffc6189082) or [this](https://blog.wizardsoftheweb.pro/tag/typescript/) fabulous series on a variety of topics related to decorators.

# The @Clamp Decorator

It's time for an example to understand the behavior that I mentioned in the beginning. The claim was that decorators are **not** instance-targeted and only called **once per class and usage**.

To proof this, we'll implement our own [property decorator](https://www.typescriptlang.org/docs/handbook/decorators.html#property-decorators) called `Clamp`.

To use decorators in TypeScript, we have to enable a compiler option called `experimentalDecorators`. The best place to do this is the `tsconfig.json`:

```json
{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
}
```

We can now create a `Clamp` decorator that we would apply to properties of type `number`. Its job is to clamp the property value within a specified upper and lower bounds.

For example, if the lower bound is `10` and the upper bound is `50`, then our decorated should clamp a value within those bounds:

```ts
clamp(5) // => 10
clamp(100) // => 50
```

We'll implement this functionality later, but first, let's shift our attention to the property decorator.

A property decorator has the following signature:

```ts
type PropertyDecoratorType = (target: any, propertyKey: string | symbol) => void;
```

It's a plain old **function** with two parameters. The `target` is the **object** that **owns** the decorated property, and the `propertyKey` is the **name** of the decorated property. Now, you might be thinking that the `target` is the instance of a class, but that's not quite the case. The `target` is simply the **prototype** of the class, but more on this in just a moment.

The signature above describes a property decorator, and it's well defined. That means the parameters are fixed, and there's no room for extending the signature. However, our decorator is supposed to be **configurable** and accept a `lower` and `upper` bound. Therefore, we have to use the a **factory function**. In other words, we enclose the decorator method within another method (factory) that defines all configurable options:

```ts
function Clamp(lowerBound: number, upperBound: number) {
  return (target: any, propertyKey: string | symbol) => {
    // logic goes here
    console.log(`@Clamp called on '${String(propertyKey)}' from '${target.constructor.name}'`);
  }
}
```

Nice, we turned a regular decorator into a decorator factory to unleash even more power. Yay!

Before implementing the logic, let's give it a spin! We'll create a class `TestBench` and decorate some properties with our homemade `@Clamp` decorator:

```ts
class TestBench {
  @Clamp(10, 20)
  a: number;

  @Clamp(0, 100)
  b: number;
}
```

That's our simple test bench. Note that we are not creating an instance of the `TestBench` class. So before we run this code, let's do a little quiz:

**Question:** What do you expect to happen?

- **A**: Nothing. The decorator doesn't get called because we are not creating an instance of the class; hence, nothing is logged.
- **B**: The decorator factory is called **once** per class; thus, there will only be **one** value printed to the console.
- **C**: The factory is called twice, once per property; hence, there will be **two** values printed to the console.
- **D**: It explodes.

Ok, drum roll... 🥁🥁🥁

Running this code gives us the following output:

```console
@Clamp called on 'a' from 'TestBench'
@Clamp called on 'b' from 'TestBench'
```

Tada! Wait, what? So it seems that our decorator function is called **twice**, once per decorated property. This means the solution to the quiz above is **C**. In case of doubt, here's a live demo:

{% stackblitz decorators-post-1 view=preview %}

The question now is, why, why is the decorator method called without us creating an instance of the class.

# Exploring decorators under the hood

To find the answer to this question, we have to dive a bit deeper and see what is actually generated by the TypeScript compiler if we use a decorator. You can either run `tsc` or copy and paste the code into the [TypeScript Playground](http://www.typescriptlang.org/play/). No matter what we do, we should get the following transpiled code:

```js
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
function Clamp(lowerBound, upperBound) {
    return (target, propertyKey) => {
        // logic goes here
        console.log(`@Clamp called on '${String(propertyKey)}' from '${target.constructor.name}'`);
    };
}
class TestBench {}
__decorate([
    Clamp(10, 20)
], TestBench.prototype, "a", void 0);
__decorate([
    Clamp(0, 100)
], TestBench.prototype, "b", void 0);
```

At first glance this is not easy to grok, especially this somewhat magical `__decorate` function defined at the top. But this method is pretty important, especially how it's consumed.

So where does `__decorate` come from and what does it do? This method comes from deep, deep, deep down the compiler and is generated when any type of decorator is used. TypeScript has a helper that produces this code, and it's called [`decorateHelper`](https://github.com/microsoft/TypeScript/blob/master/src/compiler/transformers/ts.ts#L3293-L3304). Definitely check out the source code. It's a great learning resource.

Ok, but what does it do? Simply put, it loops over every decorator that is passed in and tries to evaluate them. A detailed explanation is outside the scope of this post. Fortunately, there is an excellent [blog post](https://netbasal.com/behind-the-scenes-how-typescript-decorators-operate-28f8dcacb224) that explains this in-depth.

So let's draw our attention to the bottom of the generated code:

```js
__decorate([
    Clamp(10, 20)
], TestBench.prototype, "a", void 0);
__decorate([
    Clamp(0, 100)
], TestBench.prototype, "b", void 0);
```

That's where the `__decorate` function is consumed. Also, we can see that it's called twice, once per decorated property and both get the same `target` passed in, that is `TestBench.prototype`. The second argument is the `propertyKey` and the last argument is a property descriptor. Here, `void 0` is used to pass `undefined`. 

> The `void` operator takes an expression, evaluates it and returns `undefined`. For more information, see the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void). There is also a great [StackOverflow answer](https://stackoverflow.com/questions/7452341/what-does-void-0-mean) that explains the purpose of this tiny operator compared to just using `undefined`. **TL;DR**: JavaScript isn't a perfect world, and `undefined` is not a reserved keyword, meaning that it's just a variable name and we could assign a new value to it.

So the code above is the code that gets generated by the TypeScript compiler, and typically we would load the code in a browser where it gets executed once the file is loaded. In other words, decorators will be applied the moment the classes, in which we use decorators, are loaded. As a result, the decorator (here our property decorator) only has access to the prototype of the class and the property name, but not the instance. This is **by design**, and it all makes sense, now that we know what gets generated by the compiler.

So far the key takeaway should be that we now know why decorators are not instance-targeted and instead executed when our JavaScript gets loaded in the browser.

It's essential to be aware of this because otherwise, we might experience unexpected behavior. To understand this, we'll have to add logic to our decorator.

# The Problem

The fact that decorators are applied when the class is loaded, not when we create instances, is not incorrect and that's actually by design. So what could possibly go wrong?

To find this out, we start off by implementing the actual clamp functionality. So let's create a factory called `makeClamp` which returns a `clamp` function with an `upper` and `lower` bound. Using a factory function again here makes the functionality more reusable.

```ts
function makeClamp(lowerBound: number, upperBound: number) {
  return function clamp(value: number) {
    return Math.max(lowerBound, Math.min(value, upperBound));
  }
}
```

We can see that this factory returns a `clamp` method. Here's an example of how we could use this:

```ts
const clamp = makeClamp(0, 10);

console.log(clamp(-10)); // => 0
console.log(clamp(0));   // => 0
console.log(clamp(5));   // => 5
console.log(clamp(10));  // => 10
console.log(clamp(20));  // => 10
```

The examples above should give us a proper understanding of what the decorator is supposed to do. A class property annotated with `@Clamp` should clip the property value within an inclusive `lower` and `upper` bound.

Simply adding this to the decorator function is not enough, because we want the decorator to be operating on an instance and it's supposed to clamp the value of a property every time it's being set.

Let's say we didn't know that the `target` was only the prototype of a class, so we modify the already existing property on the target using `Object.defineProperty`. This will allow us, besides other things, to define a `getter` and `setter`, which is exactly what we need. Here's what we have to do:

1. create a desired `clamp` method using the factory `makeClamp`.
2. maintain some internal state used to store the clamped property value.
3. modify the target property using `Object.defineProperty` and provide a `getter` and `setter` so that we can intercept any modification to the value and run it through our `clamp` method.

Putting this into code could look like this:

```ts
function Clamp(lowerBound: number, upperBound: number) {
  return (target: any, propertyKey: string | symbol) => {
    console.log(`@Clamp called on '${String(propertyKey)}' from '${target.constructor.name}'`);

    // 1. Create clamp method
    const clamp = makeClamp(lowerBound, upperBound);

    // 2. Create internal state variable that holds the clamped value
    let value;

    // 3. Modify target property and provide 'getter' and 'setter'. The 'getter'
    // simply returns the internal state, and the 'setter' will run any new value
    // through 'clamp' and update the internal state.
    Object.defineProperty(target, propertyKey, {
      get() {
        return value;
      },
      set(newValue: any) {
        value = clamp(newValue);
      }
    })
  }
}
```

Let's also update our test bench, remove one property for simplicity, and create two instances of the test class. Furthermore, we'll set the property to some value:

```ts
class TestBench {
  @Clamp(10, 20)
  a: number;
}

const tb1 = new TestBench();
console.log(`Setting 'a' on TB1`)
tb1.a = 30;
console.log(`Value of 'a' on TB1:`, tb1.a);

const tb2 = new TestBench();
console.log(`Value of 'a' on TB2:`, tb2.a);
```

Running this code will print the following output:

```console
@Clamp called on 'a' from 'TestBench'
Setting 'a' on TB1
Value of 'a' on TB1: 20
Value of 'a' on TB2: 20
```

Now, this output seems a bit off, doesn't it? We create the first instance `tb1` and immediately set property `a` to `30`. This results in the `setter` to be called, which clamps the value within the specified upper and lower bound. The result should be `20`, and that's the case. So far, so good. Then we create another instance `tb2` and simply read the property, causing the `getter` to be called. Somehow this returns `20` even though we haven't set the value on the second instance. Why?

This is what I meant by _unexpected behavior_, at least if we are not aware of the fact that the `target` is not the class instance but the prototype. So any modifications on the target will affect every instance because we are globally modifying the prototype of the class. Also, the `value` that was meant to be an internal state to every decorator is **shared** across all instances, because they all **share the same decorator scope**. It is what it is, but for our use case, that's not cool.

Check out this live demo! I highly encourage you to noodle around with the code a bit.

{% stackblitz decorators-post-2 view=preview %}

# Creating instance-targeted decorators

So what do we do if we want our decorator to be instance-based? We certainly don't want to share state across instances globally.

The solution involves modifying the target property once the decorator is applied, as well as defining a property on the instance with the same property name. In other words, we define a property with a `setter` on the target prototype that will install a property with the same name, that is `propertyKey`, on the target instance once it is used for the first time.

Ok, let's take a look at the code. I have added a whole bunch of comments to make it easier to understand what's going on:

```ts
function Clamp(lowerBound: number, upperBound: number) {
  return (target: any, propertyKey: string | symbol) => {
    console.log(`@Clamp called on '${String(propertyKey)}' from '${target.constructor.name}'`);

     // Create clamp method
    const clamp = makeClamp(lowerBound, upperBound);

    // Create map to store values associated to a class instance
    const values = new WeakMap();   

    // Define property on the target with only a `setter` because we don't
    // want to read from the prototype but instead from the instance.
    // Once the value of the property is set for the first time we define
    // a property with a `getter` and `setter` on the instance.
    Object.defineProperty(target, propertyKey, {
      set(newValue: any) {
        console.log('set on target');

        // This `setter` gets called once per new instance, and only the 
        // first time we set the value of the target property.

        // Here we have access to the instance `this`, so we define 
        // a property with the same name on the class instance.
        Object.defineProperty(this, propertyKey, {
          get() {
            console.log('get on instance');
            // This `getter` gets called every time we read the instance property.
            // We simply look up the instance in our map and return its value.
            return values.get(this);
          },
          set(newValue: any) {
            console.log('set on instance');
            // This `setter` is called every time we set the value of the 
            // property on the class instance.
            values.set(this, clamp(newValue));
          }
        });

        // Finally we set the value of property on the class instance.
        // This will trigger the `setter` on the instance that we defined above.
        return this[propertyKey] = newValue;
      }
    })
  }
}
```

Essentially, we are using `Object.defineProperty` inside `Object.defineProperty` but with different objects. The first one uses the `target` which is the class prototype, and the second one uses `this` which refers to the class instance.

Also, note that we are using a `WeakMap` at the top of the decorator to store the property value for each instance. A `WeakMap` is a special kind of `Map` but the difference is that a `WeakMap` doesn't prevent an object from being garbage collected even though this object is used as the **key** in the `WeakMap`. If you want to learn more, check out [this](https://javascript.info/map-set-weakmap-weakset) fantastic blog post which explains the differences really well.

Alright, let's give this revised version of our decorator a spin and see if it is really instance-targeted and if it no longer shares state across all instances of the same class. For that, I have slightly updated our test bench and added a few comments:

```ts
// When this class gets loaded, the decorator is applied and executed.
// This will define the `setter` for the target property on the prototype
// of this class.
class TestBench {
  @Clamp(10, 20)
  a: number;
}

const tb1 = new TestBench();

// This should return `undefined` because we didn't define a `getter`
// on the target prototype for this property. We only install a `getter`
// once we set the value for the first time.
console.log(`Reading 'a' on TB1`, tb1.a);

// This calls the `setter` for `target.a` and defines a property with 
// a `getter` and `setter` on the class instance for the same property.
tb1.a = 30;

// From this moment on, every time we read the value for this property
// we would call the most inner `getter`.
console.log(`Reading 'a' on TB1`, tb1.a);

// The same applies for updating the value. This will call the `setter`
// that we defined for the property of the class instance.
console.log(`Updating 'a' on TB1`);
tb1.a = 15;

// Creating a new instance doesn't do anything
const tb2 = new TestBench();

// Remember, we have globally defined a getter for `target.a` and because we
// are operating on a new instance, the target setter will be called which
// will set up the property on the new instance with their own `getter`
// and `setter` methods.
console.log(`Setting 'a' on TB2`);
tb2.a = 5;

console.log(`Reading 'a' on TB2:`, tb2.a);

// Remains unmodified because every instance has it's own property defined
// with their own `getter` and `setter`
console.log(`Reading 'a' on TB1:`, tb1.a);
```

Tada! It seems to be working. We have just implemented our own decorator that works on an instance level rather than being prototype-based. I mean it still involves modifying the prototype, but now every decorator also operates on a single instance, and they are all isolated from one another.

Check out the final solution and definitely play around with the code:

{% stackblitz decorators-post-3 view=preview %}

# Bonus

The above illustrates a full-blown solution, but while I was writing this blog post [Netanel Basal](https://twitter.com/NetanelBasal) pointed out to me a solution that is much more concise and cleaner. It doesn't require a double call to `Object.defineProperty`, because he [found out](https://github.com/microsoft/TypeScript/issues/32395) that the return value is not ignored, as opposed to what's mentioned in the documentation, and is in fact used as an input for a call to `Object.defineProperty`.

With that in mind, we can reduce our solution from above to the following, which has the exact same behavior:

```ts
function Clamp(lowerBound: number, upperBound: number): any {
  return (target: any, propertyKey: string | symbol) => {
    const clamp = makeClamp(lowerBound, upperBound);

    // We need a unique key here because otherwise we would be
    // calling ourselves, and that results in an infinite loop.
    const key = Symbol();

    // We can return a property descriptor that is used to define 
    // a property on the target given the `propertyKey`.
    return {
      get() {
        // Read the value from the target instance using the
        // unique symbol from above
        return this[key]; 
      },
      set(newValue: any) { 
        // Clamp the value and write it onto the target instance
        // using the unique symbol from above
        this[key] = clamp(newValue);
      }
    }
  }
}
```

Now, this is pretty clean, isn't it? 🔥

Here's a live demo:

{% stackblitz decorators-post-4 view=preview %}

# Conclusion

Decorators are class and property-based, meaning they are applied and executed once per decorated property when the class gets loaded. This means the `target` is not the class instance but the prototype of the class. Any changes made to the `target` are made globally, and if we try to use the decorator scope to maintain some internal state, that state is being shared across all instances of the same class, and they all use the same decorator scope. This could lead to unexpected behavior.

However, in this article, we have seen a solution that involves a double `Object.defineProperty` with different targets to make a decorator instance-based.

Hopefully, by now, you have a better understanding of how decorators work and why they behave the way they do.

If you enjoyed this post feel free to give it a thumbs up and let me know if you have any questions or comments!

# Special Thanks

I’d like to thank [Netanel Basal](https://twitter.com/NetanelBasal) and [Manfred Steyer](https://twitter.com/ManfredSteyer) for reviewing the article and providing valuable feedback. 🙏