DEV Community

Alexandr Guzenko
Alexandr Guzenko

Posted on

All about decorators in TS

Decorators in TypeScript: An Introduction

Decorators are a powerful feature of TypeScript that can be used to annotate and modify declarations in your code. They are widely used in the Angular framework, but can also be used in other TypeScript projects. This article will provide an overview of decorators and how to use them effectively in your TypeScript code.

What are Decorators in TypeScript?

Decorators are functions that can be used to modify the behavior of a class, property, method, or parameter. They are similar to annotations in other programming languages, but with a few key differences. In TypeScript, decorators are applied using the @ symbol followed by the name of the decorator function.

Decorators can be used to:

  • Add metadata to a class, property, method, or parameter
  • Modify the behavior of a class, property, method, or parameter
  • Add additional functionality to a class, property, method, or parameter

How to Use Decorators in TypeScript

To use decorators in TypeScript, you first need to create a decorator function. The following is a simple example of a class decorator that logs the name of the class it is applied to:

function logClass(target: Function) {
  console.log(`Class ${target.name} has been created`);
}

@logClass
class MyClass {
  // Class code here
}

Enter fullscreen mode Exit fullscreen mode

In this example, the logClass decorator is applied to the MyClass class using the @ symbol. When the class is created, the logClass function will be called and the name of the class will be logged to the console.

Decorators can also be applied to properties, methods, and parameters. The following is an example of a property decorator that logs the value of the property it is applied to:

function logProperty(target: Object, propertyKey: string) {
  console.log(`Property ${propertyKey} has been created`);
}

class MyClass {
  @logProperty
  myProperty: string;
}

Enter fullscreen mode Exit fullscreen mode

In this example, the logProperty decorator is applied to the myProperty property of the MyClass class. When the property is created, the logProperty function will be called and the name of the property will be logged to the console.

Real-Life Examples of Decorators in TypeScript

Decorators are commonly used in the Angular framework to add metadata to classes, properties, methods, and parameters.

Here are some examples of decorators in TypeScript used in the Angular framework:

  1. @Component: This decorator is used to add metadata to a class that defines a component in Angular. It provides information about the component, such as its selector, template, and styles.
  2. @Input: This decorator is used to bind data from the parent component to the child component. It is applied to a property in the child component class and allows data to be passed from the parent component to the child component.
  3. @Output: This decorator is used to bind an event from the child component to the parent component. It is applied to an event emitter property in the child component class and allows events to be emitted from the child component to the parent component.
  4. @NgModule: This decorator is used to define a module in Angular. It provides metadata about the module, such as the imports, exports, and declarations of the components and services in the module.
  5. @Injectable: This decorator is used to define a service in Angular. It indicates that the class is a service that can be injected into other components or services.

Using Decorators on Classes

Class decorators are a powerful tool for adding metadata and modifying the behavior of a class. To use a class decorator, simply add the @ symbol followed by the name of the decorator function before the class declaration:

@decoratorFunction
class MyClass {
  // Class code here
}

Enter fullscreen mode Exit fullscreen mode

In the example above, the decoratorFunction is applied to the MyClass class. When the class is created, the decoratorFunction will be called and can access the class's constructor, methods, and properties.

One common use case for class decorators is adding metadata to a class. For example, in the Angular framework, the @Component decorator is used to add metadata to a class that defines a component:

@Component({
  selector: 'app-my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.css']
})
class MyComponent {
  // Component code here
}

Enter fullscreen mode Exit fullscreen mode

In this example, the @Component decorator is applied to the MyComponent class and is used to provide information about the component, such as its selector, template, and styles.

Another use case for class decorators is modifying the behavior of a class. For example, a class decorator could be used to add additional methods or properties to a class:

function addLogging(target: any) {
  const original = target.prototype.log;

  target.prototype.log = function (message: string) {
    console.log(`Message: ${message}`);
    original.apply(this, arguments);
  };
}

@addLogging
class MyClass {
  log(message: string) {
    console.log(`Logged Message: ${message}`);
  }
}

Enter fullscreen mode Exit fullscreen mode

In this example, the addLogging class decorator is applied to the MyClass class. When the class is created, the addLogging function is called and adds a new log method to the class that logs the message before calling the original log method.

Using Decorators on Methods

Decorators can also be applied to methods to modify their behavior or add additional functionality. A common use case for method decorators is to add logging to a method. The following is an example of a method decorator that logs when a method is called:

function logMethod(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log(`Method ${propertyKey} has been called`);
}

class MyClass {
  @logMethod
  myMethod() {
    // Method code here
  }
}

Enter fullscreen mode Exit fullscreen mode

In this example, the logMethod decorator is applied to the myMethod method of the MyClass class. When the method is called, the logMethod function will be called and a message will be logged to the console indicating that the method has been called.

Method decorators can also be used to modify the behavior of a method. For example, the following method decorator can be used to cache the result of a method so that it is only executed once:

function cacheResult(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  let cache = new Map();

  descriptor.value = function (...args) {
    let key = args.toString();

    if (cache.has(key)) {
      return cache.get(key);
    } else {
      let result = descriptor.value.apply(this, args);
      cache.set(key, result);
      return result;
    }
  };

  return descriptor;
}

class MyClass {
  @cacheResult
  myMethod(arg1: any, arg2: any) {
    // Method code here
  }
}

Enter fullscreen mode Exit fullscreen mode

In this example, the cacheResult decorator is applied to the myMethod method of the MyClass class. When the method is called, the cacheResult function is executed and the result of the method is cached so that it is only executed once for the given set of arguments.

In conclusion, method decorators are a powerful tool that can be used to modify the behavior of methods and add additional functionality. Whether you are adding logging, caching results, or something else entirely, method decorators can help you write clean, maintainable code in TypeScript.

Using Decorators on Properties

Decorators can also be used to modify the behavior and add metadata to properties in TypeScript. The syntax for applying a decorator to a property is similar to applying a decorator to a class, with the decorator being applied to the property using the @ symbol.

Here's an example of a property decorator that logs the value of a property whenever it is set:

function logProperty(target: any, propertyKey: string) {
  let value = target[propertyKey];

  const getter = () => {
    console.log(`Getting value for ${propertyKey}: ${value}`);
    return value;
  };

  const setter = (newValue: any) => {
    console.log(`Setting value for ${propertyKey}: ${newValue}`);
    value = newValue;
  };

  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true,
  });
}

class MyClass {
  @logProperty
  myProperty: string;
}

Enter fullscreen mode Exit fullscreen mode

In this example, the logProperty decorator is applied to the myProperty property of the MyClass class. The decorator uses the Object.defineProperty method to define a getter and setter for the property, which logs the value of the property whenever it is set or retrieved.

Using property decorators in TypeScript can be a powerful way to add functionality and modify the behavior of properties in your code. They can also be used to add metadata to properties, which can be useful in certain use cases, such as in the Angular framework.

Decorator Composition

Decorators can be combined to create new, more complex decorators. This is known as decorator composition. Decorator composition allows you to build upon existing decorators to create new, more powerful decorators that can add multiple pieces of metadata or modify behavior in multiple ways.

Here's an example of how you might use decorator composition to create a new decorator that combines two existing decorators:

function logClass(target: Function) {
console.log(`Class ${target.name} has been created`);
}

function addMetadata(metadata: any) {
return function (target: Function) {
Reflect.defineMetadata('metadata', metadata, target);
};
}

@logClass
@addMetadata({ name: 'MyClass' })
class MyClass {
// Class code here
}
Enter fullscreen mode Exit fullscreen mode

In this example, the logClass and addMetadata decorators are combined to create a new decorator that both logs the creation of the class and adds metadata to it. The order in which the decorators are applied is important, as the behavior of the decorators is applied in the order they are written. In this case, the logClass decorator is applied first, followed by the addMetadata decorator.

This example demonstrates the power of decorator composition and the ease with which it can be used to add complex behavior to your classes, methods, properties, and parameters.

In conclusion, decorators in TypeScript provide a powerful and flexible way to modify the behavior and metadata of your code. By understanding how to use decorators effectively, you can take your TypeScript code to the next level and build robust, maintainable, and scalable applications.

Best Practices for Using Decorators in TypeScript

Decorators can be a powerful tool for adding functionality and metadata to your TypeScript code. However, it is important to use them correctly and effectively in order to achieve the desired results. Here are some tips for using decorators effectively in your TypeScript code:

Tips for using decorators effectively in your TypeScript code:

  1. Keep decorators small and focused: Decorators should be focused on a specific task and should not contain complex logic. Keeping your decorators small and focused will help you to reuse them in different parts of your code.
  2. Use composition to build complex behavior: Instead of creating a single complex decorator, consider using multiple decorators to build up the desired behavior. This will make it easier to maintain your code and to reuse individual components in other parts of your code.
  3. Avoid using decorators to perform complex operations: Decorators should be used to add metadata and to modify behavior in a simple and straightforward way. If you need to perform complex operations, consider using other techniques, such as class inheritance or composition.

Common mistakes to avoid when using decorators:

  1. Applying the same decorator multiple times: Avoid applying the same decorator multiple times to a single declaration. This can lead to unexpected behavior and may make your code more difficult to maintain.
  2. Using decorators to perform complex operations: Avoid using decorators to perform complex operations, such as data validation or business logic. These operations should be performed in separate functions or classes.
  3. Not understanding the order of execution: Decorators are executed in the order in which they appear. It is important to understand the order of execution in order to ensure that your decorators are executed correctly.

By following these tips and avoiding common mistakes, you can use decorators in TypeScript effectively to add functionality and metadata to your code. Whether you are working on a large-scale Angular project or a small TypeScript project, decorators can be a powerful tool for improving the structure and functionality of your code.

Conclusion

Decorators are a powerful and versatile feature of TypeScript that can be used to add metadata, modify behavior, and add functionality to classes, properties, methods, and parameters. In this article, we've seen how to use decorators, and how to apply them to different parts of your code.

Summary of the benefits of using decorators in TypeScript:

  • Decorators provide a way to add metadata, modify behavior, and add functionality to classes, properties, methods, and parameters.
  • Decorators can make it easier to maintain your code by providing a clear and concise way to add information about your classes, properties, methods, and parameters.
  • Decorators can make your code more readable by providing a clear and concise way to add information about your classes, properties, methods, and parameters.
  • Decorators can make your code more modular and reusable by allowing you to add functionality to existing classes, properties, methods, and parameters.

Final thoughts on decorators in TypeScript:

  • Decorators are a valuable tool in your TypeScript toolkit, and they can help you write more efficient and maintainable code.
  • Decorators can be used to add metadata, modify behavior, and add functionality to your code in a clear and concise way.
  • Decorators are widely used in the Angular framework, but can also be used in other TypeScript projects.

In conclusion, decorators are a powerful and versatile feature of TypeScript that can be used to make your code more maintainable, readable, and modular. If you haven't used decorators before, I encourage you to experiment with them and see how they can improve your TypeScript code.

Top comments (0)