Typescript decorators

Created By: chatGPT

TypeScript decorators are a powerful feature that allows you to modify the behavior of classes, methods, accessors, properties, or parameters. They are essentially functions that can be attached to a target to modify its behavior in a reusable way. Decorators work well with object-oriented programming and can help in augmenting existing code without modifying it directly.
// Basic decorator definition
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Calling ${propertyKey} with`, args);
        return originalMethod.apply(this, args);
    };
}
To use decorators, enable the experimental metadata and decorator support in your tsconfig.json file by setting experimentalDecorators and emitDecoratorMetadata to true.
{
    "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}
Once decorators are enabled, you can apply them to classes or methods. For example, below is a simple use of a class decorator that counts the number of instances created.
let instanceCount = 0;

function CountInstances<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        constructor(...args: any[]) {
            super(...args);
            instanceCount++;
            console.log(`Instances created: ${instanceCount}`);
        }
    };
}

@CountInstances
class MyClass {
    constructor(public name: string) {}
}

const obj1 = new MyClass('First Instance');
const obj2 = new MyClass('Second Instance');
Method decorators can enhance or modify specific methods. In the previous example, the Log decorator can be applied to a method to log its calls. Below is how you would use it.
class Example {
    @Log
    sayHello(name: string) {
        return `Hello, ${name}!`;
    }
}

const example = new Example();
console.log(example.sayHello('World'));
Additionally, you can also create property decorators. Below is an example that validates if the value assigned to the property is a non-empty string.
function IsNonEmptyString(target: any, propertyKey: string) {
    let value: string;

    const getter = () => value;
    const setter = (newValue: string) => {
        if (!newValue) {
            console.error(`${propertyKey} cannot be empty!`);
        } else {
            value = newValue;
        }
    };

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

class User {
    @IsNonEmptyString
    name: string = '';
}

const user = new User();
user.name = ''; // Will log an error
user.name = 'John'; // Will set the value
Introduction And SetupVariablesData TypesAnyUnknownVoidNeverStringNumberBooleanArrayTupleEnumObjectInterfaceType AliasMethodsFunctionArrow FunctionReturn TypeParametersDefault ParametersOptional ParametersRest ParametersControl FlowIf StatementElse StatementSwitch StatementFor LoopWhile LoopDo While LoopFor...of LoopFor...in LoopBreak StatementContinue StatementFunctionsFunction OverloadingRecursive FunctionExpression FunctionOperatorsArithmetic OperatorsAssignment OperatorsComparison OperatorsLogical OperatorsBitwise OperatorsConditional (ternary) OperatorData StructuresArrayTupleObjectMapSetCommentsSingle Line CommentMulti Line CommentSyntaxType AnnotationsType InferenceNamespaceModuleExportImportDecoratorsAmbient DeclarationsEvent HandlingEvent ListenersAddEventListenerRemoveEventListenerError HandlingTry...catch StatementThrow StatementFinally BlockCustom Error TypesAsync AwaitPromisesGenericsConditional TypesMapped TypesIntersection TypesUnion TypesLiteral TypesDiscriminated UnionsType GuardsTypeof GuardInstanceof GuardAssertion FunctionsModule Augmentation