In TypeScript, decorators are a powerful feature that allows developers to modify the behavior of classes, methods, properties, and parameters at design time. Inspired by annotations in other programming languages, decorators provide a way to add metadata or modify the functionality of a class without altering its original code. Introduced in ECMAScript as a stage 2 proposal, decorators in TypeScript can be used for a variety of purposes, such as logging, validation, and dependency injection. One of the most notable uses of decorators is in Angular, where they are heavily utilized to define components, services, directives, and other key building blocks of the framework. In this article, we will explore what decorators are, how they work in TypeScript, and how they are applied in Angular to create dynamic, maintainable applications.
Decorators in TypeScript are like holiday gift wraps. Imagine you have a plain gift (your class or method). The gift itself is functional and useful, but you want to make it more special, presentable, or festive. You could just give it as is, but instead, you add decorations like colorful wrapping paper, ribbons, and a bow (the decorator). The gift itself has not changed, but the wrapping (the decorator) enhances its appearance and can even include special tags (metadata) that tell the recipient more about what is inside. Similarly, decorators in TypeScript add extra features, behaviors, or metadata to your classes or methods without changing the core functionality – making your code more festive, flexible, and ready for the holidays!
A decorator is a function that applies to a class, method, accessor, property, or parameter.
They are prefixed with the ‘@’ symbol and are used to add meta-data or modify the behavior of elements.
Decorators are a powerful feature both in TypeScript and in Angular that provide a declarative way to add metadata and modify the behavior of classes, methods, properties and parameters.
What is meant by Declarative?
Declarative programming is a style of programming where you describe what you want the program to do instead of how to do it. It focuses on expressing the desired outcome or behavior without needing to specify the detailed steps to achieve it.
General example
HTML is a declarative language You describe the structure of a webpage (e.g., <div>, <p>, <h1>, etc.), but you're not specifying how the browser should render those elements or how the DOM (Document Object Model) should be manipulated.
Example in Angular
The @Component shows that the class is an Angular component. However, Angular handles the underlying operations, you write the logic to tell it how to take care of it.
Type of decorators
1. Class decorators
A class decorator is applied to the constructor of the class.
It is useful for modifying the class or adding metadata.
Example in TS
function MyClassDecorator(target: Function) {
// Modify class constructor or add metadata
}
@MyClassDecorator
class MyClass {
constructor() {
console.log('MyClass instance created');
}
}
Example in Angular
In Angular, the most common class decorator is the ‘@Component’ and ‘@Injectable’
- The @Component decorator is used to define Angular components. It provides Angular with the metadata it needs to create and render the component.
- The @Injectable decorator is used to define a service class that can be injected into other components or services. It tells Angular's dependency injection system that the class is injectable.
- The @NgModule decorator is used to define an Angular module. It bundles components, directives, pipes, and services into a cohesive unit.
2. Method decorators
A method decorator is applied to methods inside a class. It can be used to modify the behavior of the method or add additional functionality.
Example in TS
function MyMethodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// Modify method behavior here
}
class MyClass {
@MyMethodDecorator
myMethod() {
console.log('Method called');
}
}
An example of logging is whenever a function is called,
function LogMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling method ${propertyKey} with arguments: ${args}`);
return originalMethod.apply(this, args);
};
}
class MyClass {
@LogMethod
greet(name: string) {
console.log(`Hello, ${name}`);
}
}
const obj = new MyClass();
obj.greet('Alice');
// Output:
// Calling method greet with arguments: ["Alice"]
// Hello, Alice
Example in Angular
The '@HostListener' decorator allows you to listen to events on the host element of the component.
3. Parameter decorators
A parameter decorator is applied to a method parameter. It cannot modify the value of the parameter but can access and store metadata.
Example in TS
function MyParameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
// Store or manipulate metadata about the parameter
}
class MyClass {
greet(@MyParameterDecorator name: string) {
console.log(`Hello, ${name}`);
}
}
Example. A parameter decorator to log the index of parameters.
function LogParameter(target: any, propertyKey: string, parameterIndex: number) {
console.log(`Parameter ${parameterIndex} of method ${propertyKey} has been decorated.`);
}
class MyClass {
greet(@LogParameter name: string, @LogParameter age: number) {
console.log(`Hello, ${name}, age: ${age}`);
}
}
const obj = new MyClass();
obj.greet('Alice', 30);
// Output:
// Parameter 0 of method greet has been decorated.
// Parameter 1 of method greet has been decorated.
// Hello, Alice, age: 30
Example in Angular
The '@Inject' decorator allows you to listen to explicitly specify which dependency should be injected into a class.
Advantages of using decorators
Separation of concerns
Decorators allow you to separate the logic for defining or modifying from the core functionality of a class. This makes the code easier to read and maintain as the behavior is defined outside the business logic
For example, in Angular, decorators like @Component and @Injectable help define how a class should behave (e.g., as a component or service) without cluttering the class itself with configuration details.
Code reusability
Decorators promote reusability by allowing you to encapsulate functionality in a reusable, declarative format. Once you create a custom decorator, you can apply it to multiple classes, methods, or properties without duplicating logic.
For example, if you create a custom logging decorator that logs method calls, you can apply it to multiple methods across different classes.
Simplify configuration
In Angular, decorators provide a concise way to configure your components, services, modules, and other elements. Instead of writing a lot of boilerplate configuration code, decorators let you define metadata in a declarative way.
For example, the @Injectable decorator allows you to inject a service. However, it does not go into the details or even the code of how it is done. Adding the decorator makes it behave in this way.
Conclusion
Despite not being native in Javascript, the concept of decorator is a feature in TypeScript and is heavily used in Angular.
Decorators in TypeScript are an advanced feature that can significantly enhance the flexibility and maintainability of your code. By adding metadata or modifying the behavior of classes and methods, decorators help you write cleaner, more declarative code. In Angular, decorators are a cornerstone of the framework, allowing developers to define the structure and behavior of components, directives, services, and more. While decorators may take some time to master, their ability to encapsulate functionality and reduce boilerplate code makes them a powerful tool in modern TypeScript development. As you continue working with Typescript and Angular, understanding and using decorators will streamline your development process and enable you to build more scalable and modular applications.