- TypeScript Tutorial
- TypeScript Home
- TypeScript Introduction
- TypeScript Setup
- TypeScript First Program
- TypeScript vs JavaScript
- TypeScript Data Types
- TypeScript Type Inference
- TypeScript Type Annotations
- TypeScript Interfaces
- TypeScript Enums
- TypeScript Type Aliases
- TypeScript Type Assertions
- TypeScript Variables
- TypeScript Functions
- TypeScript Functions
- TypeScript Optional Parameters
- TypeScript Default Parameters
- TypeScript Rest Parameters
- TypeScript Arrow Functions
- Classes and Objects
- Introduction to Classes
- Properties and Methods
- Access Modifiers
- Static Members
- Inheritance
- Abstract Classes
- Interfaces vs Classes
- Advanced Types
- TypeScript Union Types
- TypeScript Intersection Types
- TypeScript Literal Types
- TypeScript Nullable Types
- TypeScript Type Guards
- TypeScript Discriminated Unions
- TypeScript Index Signatures
- TypeScript Generics
- Introduction to Generics
- TypeScript Generic Functions
- TypeScript Generic Classes
- TypeScript Generic Constraints
- TypeScript Modules
- Introduction to Modules
- TypeScript Import and Export
- TypeScript Default Exports
- TypeScript Namespace
- Decorators
- Introduction to Decorators
- TypeScript Class Decorators
- TypeScript Method Decorators
- TypeScript Property Decorators
- TypeScript Parameter Decorators
- Configuration
- TypeScript tsconfig.json File
- TypeScript Compiler Options
- TypeScript Strict Mode
- TypeScript Watch Mode
TypeScript Property Decorators
Property decorators in TypeScript offer a powerful way to add metadata or change the behavior of class properties declaratively. Instead of writing boilerplate code inside your constructor or methods, you can "annotate" properties to handle tasks like validation, logging, or data transformation. This approach leads to cleaner, more maintainable code by separating your business logic from cross-cutting concerns.
What is a Property Decorator?
A property decorator is a specialized function that sits right above a class property. Unlike method decorators, property decorators do not have access to the property descriptor (like value or writable) as an argument. Instead, they are called when the class is defined, not when an instance is created.
The decorator function receives exactly two arguments:
- target: This is the prototype of the class (for instance properties) or the constructor function (for static properties).
- propertyKey: The actual name of the property as a string or symbol.
new.
Enabling Property Decorators in TypeScript
By default, TypeScript considers decorators an experimental feature. To use them, you must explicitly enable them in your project configuration.
Example: Enabling Property Decorators
{
"compilerOptions": {
"target": "ES6",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
- The
experimentalDecoratorsflag is required to prevent the TypeScript compiler from throwing errors when it encounters the@symbol.
Syntax of Property Decorators
A property decorator is simply a function. Because it doesn't return a property descriptor, it's often used to record metadata about the property or to redefine the property on the prototype using Object.defineProperty.
function PropertyDecorator(target: any, propertyKey: string) {
// 'target' is the prototype
// 'propertyKey' is the name of the variable
console.log("Decorating:", propertyKey);
}
this inside the decorator function. Because the decorator runs at class definition time, this does not refer to an instance of the class.
Applying Property Decorators
Applying a decorator is as simple as prefixing the property name with @ followed by the function name.
Example: Applying a Simple Property Decorator
function LogProperty(target: any, propertyKey: string) {
console.log(`Property "${propertyKey}" has been registered.`);
}
class Example {
@LogProperty
name: string;
}
// Console Output: Property "name" has been registered.
const example = new Example();
- The
@LogPropertydecorator runs as soon as theExampleclass is parsed by the JavaScript engine.
Modifying Property Behavior with Decorators
Since property decorators don't give you direct access to the property value, you must use Object.defineProperty to intercept the get and set actions. This is how you can transform data—for example, making a string automatically uppercase.
Example: Modifying Property Behavior
function UpperCase(target: any, propertyKey: string) {
// We create a private variable to store the value
let value: string;
const getter = function() {
return value;
};
const setter = function(newValue: string) {
value = newValue.toUpperCase();
};
// Redefine the property on the class prototype
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
class Example {
@UpperCase
name: string;
}
const example = new Example();
example.name = "hello world";
console.log(example.name); // Output: HELLO WORLD
Object.defineProperty in a decorator, always set enumerable and configurable to true unless you have a specific reason to hide or lock the property.
Property Decorators with Parameters
Sometimes you need to pass data into your decorator. To do this, you use a Decorator Factory. This is a function that returns the actual decorator function.
Example: Property Decorator with Parameters
function MaxLength(limit: number) {
return function(target: any, propertyKey: string) {
let value: string;
const getter = () => value;
const setter = (newValue: string) => {
if (newValue.length > limit) {
console.error(`Error: ${propertyKey} cannot be longer than ${limit} chars.`);
} else {
value = newValue;
}
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter
});
};
}
class UserProfile {
@MaxLength(10)
username: string;
}
const user = new UserProfile();
user.username = "TypescriptMaster"; // Output: Error: username cannot be longer than 10 chars.
- This pattern is widely used in frameworks like NestJS or TypeORM for configuration.
Using Property Decorators for Validation
Validation is one of the most practical use cases for decorators. You can prevent invalid data from ever reaching your class instances by throwing errors in the setter.
Example: Property Decorator for Validation
function Required(target: any, propertyKey: string) {
let value: any;
const getter = () => value;
const setter = (newValue: any) => {
if (newValue === null || newValue === undefined) {
throw new Error(`Property ${propertyKey} is required and cannot be null.`);
}
value = newValue;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter
});
}
class Product {
@Required
title: string;
}
const p = new Product();
try {
p.title = null; // Throws error
} catch (e) {
console.log(e.message);
}
Using Property Decorators with Accessors
While we usually apply decorators to simple properties, they can also be applied to Accessors (getters and setters). Note that TypeScript only allows you to decorate either the getter or the setter for a single property name, not both.
Example: Property Decorators with Accessors
function ReadOnly(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.writable = false;
}
class Configuration {
private _apiKey: string = "12345-ABCDE";
@ReadOnly
get apiKey() {
return this._apiKey;
}
}
- When applied to an accessor, the decorator receives a third argument: the Property Descriptor, allowing you to easily toggle settings like
writable.
Summary
- Property decorators provide a declarative way to intercept, validate, or transform class data.
- They take two arguments: the target (prototype) and the propertyKey.
- To modify values, you typically use
Object.definePropertyto create custom getters and setters. - Decorator Factories allow you to pass custom arguments into your decorators for flexible configurations.
- They are essential tools for building modern, scalable TypeScript applications and are heavily used in major frameworks.