Mastering Advanced TypeScript: Understanding Class Decorators
Written on
Chapter 1: Introduction to TypeScript Decorators
In this article, we'll explore TypeScript decorators, a feature that allows us to annotate class declarations and their members effectively. Following the principles of the structural design pattern known as the Decorator pattern, these decorators offer a versatile alternative to subclassing for extending functionality.
To further expand your knowledge of advanced TypeScript features, feel free to check out my other articles.
The Decorator pattern is a powerful tool that helps prevent the proliferation of subclasses. It allows for the dynamic addition of responsibilities to an object, providing a more flexible approach to functionality extension without creating numerous subclasses.
The Decorator pattern is particularly useful in various scenarios, such as:
- Avoiding the creation of excessive subclasses when managing different combinations of behaviors.
- Dynamically adding responsibilities to individual objects while maintaining the integrity of other objects.
- Assigning new behaviors to objects at runtime without disrupting existing code that relies on these objects.
Section 1.1: Understanding TypeScript Decorators
With the introduction of classes in TypeScript and ES6, there was a need to support the annotation and modification of classes and their members using decorators. This experimental feature enables us to decorate classes, methods, properties, accessors, and parameters.
Decorators are still an experimental feature and may change in future releases. — typescriptlang.org
To enable decorators, you must set the experimentalDecorators option to true in the compilerOptions section of your tsconfig.json file. Once configured, you can create decorator functions prefixed with @.
Decorators follow the format @expression, where expression must evaluate to a function that will be invoked at runtime with information about the decorated element.
For example, let's create a simple class decorator function named logMessage. This function will return an inner function designed to print a message whenever an instance of the class is created. This inner function concept is known as a Decorator factory and allows us to pass parameters.
Now you can annotate any class with @logMessage('some Message'). For instance, if we have a User class and apply the decorator, we will see a console message every time a new user is instantiated.
Section 1.2: Exploring Decorator Types
While we've seen a straightforward example of a class decorator, TypeScript decorators can be far more complex and beneficial. There are five primary types of decorators:
- Class Decorators
- Method Decorators
- Accessor Decorators
- Property Decorators
- Parameter Decorators
Given the complexity of TypeScript decorators, it makes sense to examine each type in detail. In this article, we'll focus on Class Decorators and engage in a code challenge to solidify our understanding.
Chapter 2: Deep Dive into Class Decorators
In our previous example, we examined a basic class decorator. Now, let's enhance our User class by implementing an @admin decorator to extend its functionality.
When applying a decorator to a class, the class constructor is received as the first parameter. You can override or add new properties by returning a new class that extends the original constructor.
Here's how we can define our @admin decorator:
This decorator can be applied to our User class, adding the onlyAdminsFunction to it. Notably, multiple decorators can be applied to a single class. When a new User instance is created, we can invoke the onlyAdminsFunction().
However, it's important to note that the decorator does not alter the TypeScript type of the class, which may lead to TypeScript errors. In such cases, we might need to use @ts-ignore to bypass type-checking.
Section 2.1: Code Challenge: Implementing Additional Properties
We have previously extended our User class with the onlyAdminsFunction(). Now, let's add extra properties: image and displayName, without modifying the existing class. To achieve this, we'll create a @displayable decorator that modifies our class dynamically.
This new decorator function will accept two parameters and assign them to the properties of the newly created class object. Remember that decorators do not alter the TypeScript type, allowing us to use @ts-ignore to access these new properties.
Here’s the initial code with errors that can be resolved by creating the correct displayable class decorator function.
Solution
The key challenge in this exercise lies in wrapping another function around our decorator, as we are passing parameters to the @displayable annotation—this is our Decorator factory.
Following this, we can extend the User class to include the additional properties displayName and image, assigning values based on the parameters provided by the decorator.
Final Thoughts
I hope you found this article insightful. I'm always available for questions and welcome feedback! Connect with me via LinkedIn, follow me on Twitter, or subscribe for updates via email.
For unlimited access to content on Medium, feel free to sign up using this link; I’ll earn a small commission at no extra cost to you.
About the Author
I am a Software Engineering Analyst at Accenture Song, constantly seeking talented developers. If you're interested in joining us, don’t hesitate to reach out!
What drives me is the passion to create impactful solutions. For instance, if you struggle to find information from your browsing history, check out my Web Highlights Chrome Extension, designed to boost your productivity by organizing your research efficiently. Highlight text on any webpage or PDF, and sync it directly to the web app at web-highlights.com for easy access from anywhere.
Further Reading
Explore more content at PlainEnglish.io, and sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord. Interested in Growth Hacking? Check out Circuit.