DEV Community

Cover image for TypeScript Functional Decorators w/ usecases
Suren
Suren

Posted on

TypeScript Functional Decorators w/ usecases

Generally, decorators in programming are used to add some additional meaning/function to a method, variable or a class.

I have used a lot of decorators while working in Spring Boot. I am currently learning Typescript and decorators fascinated me. Here is a small illustration of my use case for typescript decorators for functions.

Consider a Human class which maintains the stamina attribute and it is manipulated by run and rest methods. We need to capture the value of stamina before and after both, the actions - run and rest. I have added snippets for the code with and without decorators.

Code Before Decorators

class Human {
    stamina: number;
    constructor(stamina: number) {
        this.stamina = stamina;
    }

    run(requiredStamina: number): number {
        return (this.stamina -= requiredStamina);
    }

    rest(addedStamina: number): number {
        return (this.stamina += addedStamina);
    }
}

const human = new Human(10);
console.log(`Stamina before run: ` + human.stamina);
human.run(1);
console.log(`Stamina after run: ` + human.stamina);
console.log(`Stamina before rest: ` + human.stamina);
human.rest(12);
console.log(`Stamina after rest: ` + human.stamina);
console.log(`Stamina before run: ` + human.stamina);
human.run(20);
console.log(`Stamina after run: ` + human.stamina);
// Stamina before run: 10
// Stamina after run: 9
// Stamina before rest: 9
// Stamina after rest: 21
// Stamina before run: 21
// Stamina after run: 1
Enter fullscreen mode Exit fullscreen mode

The above code looks good but soon the file would be cluttered with boilerplates.

Code with Decorators

class Human {
    stamina: number;
    constructor(stamina: number) {
        this.stamina = stamina;
    }

    @log
    run(requiredStamina: number): number {
        return (this.stamina -= requiredStamina);
    }

    @log
    rest(addedStamina: number): number {
        return (this.stamina += addedStamina);
    }
}
function log(
    target: Object,
    propertyKey: string,
    descriptor: TypedPropertyDescriptor<any>
) {
    const originalMethod = descriptor.value; // save a reference to the original method

    // NOTE: Do not use arrow syntax here. Use a function expression in
    // order to use the correct value of `this` in this method (see notes below)
    descriptor.value = function (...args: any[]) {
        // pre
        console.log(`Stamina before ${propertyKey}: ` + args);
        // run and store result
        const result = originalMethod.apply(this, args);
        // post
        console.log(`Stamina after ${propertyKey}: ` + result);
        // return the result of the original method (or modify it before returning)
        return result;
    };

    return descriptor;
}

const human = new Human(10);
human.run(1);
human.rest(12);
human.run(20);
// Stamina before run: 1
// Stamina after run: 9
// Stamina before rest: 12
// Stamina after rest: 21
// Stamina before run: 20
// Stamina after run: 1
Enter fullscreen mode Exit fullscreen mode

I have added comments in the snippet explaining the flow of data in the decorator! Simply put, decorators are invoked before the function is invoked and also has access to the arguments and the return value which could be used by the decorator.

There an N use cases for decorators in general. Let me know some interesting use case in the comment or reach out to me at radnerus93.

Oldest comments (8)

Collapse
 
error404sp profile image
Shreyasi Patil

Great article🙌🙌

Collapse
 
radnerus profile image
Suren

Thank you, Shreyasi✌️😉

Collapse
 
akashkava profile image
Akash Kava

At Web Atoms, we are using decorator as follow,

Bindable Property


   @BindableProperty
   public value: any;

This turns member into property with getter/setter with ability to refresh the UI whenever it is set.

Dependency Injection


   @Inject
   public userService: UserService;

DI container automatically injects service for specified type.

Watch Decorator


   @Watch
   public get fullName() {
       return `${this.model.firstName} ${this.model.lastName}`;
   }

Full name property automatically freshes the UI bound to this property when firstName or lastName changes.

Validation

   @Validate
   public get errorFirstName() {
       return this.model.firstName ? "" : "First name is required";
   }

Validate decorator helps in setting up validation that updates UI automatically when property is changed.

AJAX Services

@DISingleton()
@BaseUrl("/api/v2020/app/")
export default class TaskService extends BaseService {

    @Get("user")
    public getUser(): Promise<IUser>;

    @Get("tasks")
    public getTasks(
        @Query("search") search: string,
        // default value should be specified in
        // decorator and not in argument declaration
        @Query("status", "open") status?: string
    ): Promise<ITask[]>

    @Get("tasks/{id}/attachments")
    public getAttachments(
        @Path("id") id: number
    ): Promise<ITaskAttachment[]>;

    @Put("tasks")
    public createTask(@Body task: ITask): Promise<ITask>;

    @Post("tasks/{id}/attachments")
    public uploadAttachment(
        @Path("id") id: number,
        @Body att: IAttachment,
        cancelToken: CancelToken): Promise<void>;
}

Data Loading

This is a bit complex, it handles loading of data and canceling previous request automatically.

   @Load({ watch: true })
   public async loadData(ct: CancelToken) {
        const search = this.search;
        this.list = await this.userService.loadUsers(search, ct);
   }

So whenever search is updated, this method is called automatically and UI refreshes it, it discards (aborts) previous ongoing request.

For more information...

Collapse
 
radnerus profile image
Suren

Interesting implementations, Akash!

Collapse
 
bravemaster619 profile image
bravemaster619 • Edited

I'm already familiar with decorators in angular. It was this reason behind that. Thanks for great article!

Collapse
 
radnerus profile image
Suren

Thank you, Bravemaster❣️

Collapse
 
arnaudcortisse profile image
Arnaud Cortisse • Edited

Nice intro to the topic!

Collapse
 
radnerus profile image
Suren

Glad this helped you :)