Skip to content

Experimental Typescript Decorator

Introduction

We all know or have heard about the Decorator pattern. Programming languages like Python have built-in support for decorators or similar features while in other language as C#, Java, the concept of decorators may differ slightly, they all serve the purpose of modifying or enhancing the behavior of classes, methods, or functions. In Typescript, there is a built-in decorator and it is still under experimental stage 3 in Typescript 5.

Let’s take a look at it and see what it can do by making a simple combat version of one of my favorite video games, Final Fantasy (classic one, not the lame new Final Fantasy XVI). We will build a combat between Tifa and Tonberry with a log of damage with  Vue3 and Typescript decorator.

Set up

Typescript decorator is not default active so you need to turn it on by enable the experimentalDecorators compiler options in tsconfig.json :

// tsconfig.json 
{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true } }

We want to use it in Vue3 project so we need to update file tsconfig.app.json instead with:

{
  "compilerOptions": {
    	"experimentalDecorators": true,
    	"target": "ES5",
    	"emitDecoratorMetadata": true,
	}
  }
}

Typescript Method Decorator

Typescript decorator is a “function that returns the expression that will be called by the decorator at runtime”.

First, let’s learn how to write method decorators. We can write it as a factory or a function itself. The method decorator takes 3 arguments: target, propertyKey and descriptor.

1. Target: constructor function of the class for a static member, or the prototype of the class for an instance member.

2. PropertyKey: name of the member.

3. Descriptor : is an interface of PropertyDescriptor . This is where we can use the originalMethod from descriptor.value.

Coding

To build a combat we need to create a BattleField Component. In the battle field, we will have a Player component, Monster component with name, photo and HP of the player and the monster. Last thing is the log damage board to record each attack.


Player Component

<script setup lang="ts">
import { Player } from '@/classes/player';

interface Props {
	player: Player;
}

const props = defineProps<Props>();
</script>

<template>
	<div class="d-flex">
    	<img class="tifa" :src="photo" />
    	<v-card class="p-card">
        	<p class="text-h5">{{ props.player.name }}</p>
        	<p>HP: {{ props.player.hp }}</p>
    	</v-card>
	</div>
</template>

Monster Component

<script setup lang="ts">
import { Monster } from '@/classes/monster';

interface Props {
	monster: Monster;
}

const props = defineProps<Props>();
</script>

<template>
	<div class="d-flex">
    	<v-card class="m-card">
        	<p class="text-h5">{{ props.monster.name }}</p>
        	<p>HP: {{ props.monster.hp }}</p>
    	</v-card>
    	<img class="monster" :src="photo" alt="" />
	</div>
</template>

Now we need 2 classes, player and monster class. Each class will have attack and take damage methods.

Monster Class

import type { Player } from './player';

export class Monster {
	constructor(public name: string, public hp: number) {}

	public attact(player: Player) {
    	const power = Math.ceil(Math.random() * 100);
    	player.takeDamage(power);
	}

	public takeDamage(damage: number) {
    		this.hp -= damage;
	}
}

Player Class

import type { Monster } from './monster';

export class Player {
	constructor(public name: string,public hp: number) {}

	public attact(monster: Monster) {
    	const power = Math.ceil(Math.random() * 100);
    	monster.takeDamage(power);
	}
    
public takeDamage(damage: number) {
    		this.hp -= damage;
	}
}

Player and Monster have attack  and takeDamage . When the user clicks on Tifa attack button. Player will trigger the attack  method and the monster being attacked will trigger takeDamage and reduce monster HP. So we can have multi players and monsters on the same battlefield.

Now the main object is building a decorator named logDamage so when a player or monster is attacked. Damage received will be displayed on screen.

import { addDamageLog } from '../utils/damageLogBoard';

export function logDamage() {
	return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    	// Decorator
    	const originalMethod = descriptor.value;

    	descriptor.value = function (damage: number) {
        	const damageReceiver = this.name as string; 
// This is refers to the instance of the class
        	addDamageLog(`${damageReceiver} took ${damage} damage!`);
        	return originalMethod.apply(this, [damage]);
    	};
	};
}

Then we have to apply this to the method takeDamage of Player and Monster class so whenever takeDamage triggers. The damage will be logged on screen by decorator logDamage

// Player Class
// Monster Class
import { logDamage } from './decorator';

@logDamage()
public takeDamage(damage: number) {this.hp -= damage; }

Now we have an epic battle between Tifa and Tonberry, a quite super strong monster in FF series. Another cool thing we can develop from here is apply element system to combat by a decorator, apply to attack and defend the character element bonus damage will be added.

Conclusion

Typescript decorator is very cool and will be another milestone of its evolution. There is still no official release date but Angular has been using it. So if you plan to use it for Production, be cautious because major changes may come and a lot of fixes may be required.

Would you like to read more articles by Tekos’s Team? Everything’s here.

References

Author

Nathan Le Avatar

Leave a comment

Your email address will not be published. Required fields are marked *

Comment
Name
Email
Website