console.log("hello world!")
						
					

Demystifying Decorators

How do @Decorators() really work?

And what can they be used for? 🤷

						
							import "reflect-metadata";
							import { Injectable, createContainer } from 'slim-di';
							
							@Injectable()
							class FishingGear {}

							@Injectable()
							class FishingBoat {}

							@Injectable()
							class FisherManRoot {
								constructor(
									private readonly gear: FishingGear, 
									private readonly boat: FishingBoat
								){}
							}

							function main(){
								const container = createContainer(FisherManRoot);
								/* Logs true */
								console.log(container.get(FishingBoat) === container.get(FishingBoat))
							}
							main()
						
					

⚠️ Disclaimer! ⚠️

Typescript 5.0 just came out!

What is a decorator?

  • Property Decorator
    												
    													class MyClass {
    														@Input()
    														myProp: string;
    													}
    												
    											
  • Method Decorator
    												
    													class MyClass {
    														@Computed()
    														myMethod(){}
    													}
    												
    											
  • Class Decorator
    												
    													@Component()
    													class MyComponent {}
    												
    											
  • Accessor Decorator
    												
    													class MyComponent {
    														@AccessorDecorator()
    														get prop(){}
    													}
    												
    											
  • Parameter Decorator
    												
    													class MyComponent {
    														prop(@Get("id") id: string){}
    													}
    												
    											
							
								/* My first method decorator 🎉 */
								function log() {
									console.log("What's up JS-Meetup? ⚡️🚀")
								}
								log();
							
						
company.ts
							
								function log(target: any, propertyKey: string, descriptor: any) {
									console.log("What's up JS-Meetup? ⚡️🚀");
								}
								
								class Company {
									@log
									calculateSalary(salary: number) {
										return salary * 0.2;
									}
								}							
							
						
company.ts (Method Decorator)
						
							function log(
								target: Object,
								propertyKey: string,
								descriptor: PropertyDescriptor
							) {
								const originalMethod = descriptor.value;
							
								descriptor.value = function (this: any, ...args: any[]) {
									console.log(`INFO: Running function ${propertyKey} with input ${args}`);
									const res = originalMethod.call(this, ...args);
									console.log(`INFO: function ${propertyKey} ran with result ${res}`);
									return res;
								};
							}
							
							class Company {
								@log
								calculateSalary(salary: number) {
									return salary * 0.2;
								}
							}
							
							const c = new Company();
							c.calculateSalary(100);
							c.calculateSalary(75);						
						
					
company.ts (Property Decorator)
						
							/* Method Decorator */
							function log(
								target: Object,
								propertyKey: string,
								descriptor: PropertyDescriptor
							) {
								const originalMethod = descriptor.value;
							
								descriptor.value = function (this: any, ...args: any[]) {
									console.log(`INFO: Running function ${propertyKey} with input ${args}`);
									const res = originalMethod.call(this, ...args);
									console.log(`INFO: function ${propertyKey} ran with result ${res}`);
									return res;
								};
							}
							/* Property Decorator */
							function withBonus(target: Object, propertyKey: string) {
								Object.defineProperty(target, propertyKey, {
									get() {
										return this.value * 1.2;
									},
									set(value) {
										this.value = value;
									},
								});
							}
							
							class Company {
								@withBonus
								public total: number = 0;
								
								@log
								calculateSalary(salary: number) {
									return salary * 0.2;
								}
								@log
								addExpense(value: number){
									this.total += value;
								}
							}
							
							const c = new Company();
							const salary = c.calculateSalary(400);
							c.addExpense(salary);
							console.log("My total is:", c.total);												
						
					

When should I create my own decorators?

Short answer: Never!

Use cases are very rare!

Let the frameworks/libraries handle it! 🚀

  • Component frameworks - Angular
  • Dependency injection - Nest.js
  • Reactivity - stencil.js

🤷‍♀️ High abstractions which can create a lot of confusion if abused 🤷

Do you know what this code is doing?

							
								@Freeze
								class Company {
									constructor(
										@Inject("Config.BASE_URL")
										private readonly baseUrl: string,
										@Optional()
										private readonly service: HttpService
									){}
									@Thing()
									prop: string;

									@FancyLog()
									calculateSalary(
										@Validate() value: number, 
										@Heyo() isBigBoss: boolean
									){
										/* Code here */
									};
									/**
										...
									**/

								}
							
						

slim-di.ts 💉

						
							import "reflect-metadata";

							function Injectable(): ClassDecorator {
								return function (target) {
									return target;
								};
							}
							
							/* Stolen from the Angular project 😏 */
							export interface Type<T = unknown> extends Function {
								new (...args: any[]): T;
							}

							export interface DIContainer {
								get<T = unknown>(token: Type<T>): T;
							}

							export const createContainer = <T>(root: Type<T>): DIContainer => {
								const container = new Map<Type, any>();

								const instantiateDeps = (token: Type): unknown => {
									if (container.get(token)) {
										return container.get(token);
									}

									const dependencyMetadata: Type[] =
										Reflect.getMetadata("design:paramtypes", token.prototype.constructor) ??
										[];

									const instance = new token(
										...dependencyMetadata.map((it) => instantiateDeps(it))
									);
									container.set(token, instance);
									return instance;
								};

								instantiateDeps(root);

								return {
									get(token) {
										return container.get(token);
									},
								};
							};

							@Injectable()
							class FishingGear {}

							@Injectable()
							class FishingBoat {}

							@Injectable()
							class FisherManRoot {
								constructor(
									private readonly gear: FishingGear,
									private readonly boat: FishingBoat
								) {}
							}

							function main() {
								const container = createContainer(FisherManRoot);
								/* Logs true */
								console.log(container.get(FishingBoat) === container.get(FishingBoat));
							}
							main();
						
					

Thank you, Questions?


Presentation
slim-di