Decorator
Setup
To enable decorator, you need to use
{
"compilerOptions": {
...
"experimentalDecorators": true,
},
}
in tsconfig.json. Without it the value passed in the decorator function is not correct.
How to use
Constructor decorator
Constructor decorator are the decorator that was used for classes. For example:
import { Service } from "./dependencyInjection";
@Service
class Student {
private name: string;
constructor() {
this.name = "some student";
}
getName() {
return this.name;
}
}
The @Service here is constructor decorator
To declare a constructor decorator, we use the following syntax:
export const Service = <T extends { new(...args: any[]): any }>(constructor: T)=> {
dependencyManager.register(constructor.name, new constructor());
}
Field decorator
Field decorator takes in different arguments. For example:
import { Autowired } from "./dependencyInjection";
class MyClass {
@Autowired
private student: Student;
method() {
console.log("my method", this.student.getName())
}
}
We can code it like this:
export const Autowired = (target: any, key: string) => {
Object.defineProperty(target, key, {
get: function() {
// Search for className
const instance = dependencyManager.resolve(Reflect.getMetadata('design:type', target, key).name);
if (!instance) {
throw new Error(`Instance ${key} is not autowired`)
}
return instance;
},
})
}
In here target will be the actual object, in this case the instance of MyClass and key will be student as the field name.
In here we use Reflect to get the type name of student which is Student since in Service we store it as class name.
To use this Reflect with meta-data type, we need to turn on
{
"compilerOptions": {
...
"emitDecoratorMetadata": true
},
}
And install reflect-metadata
Method decorator
import { Initialise } from "./dependencyInjection";
class School {
private name: string;
constructor(name: string) {
this.name = name;
}
@Initialise
initialise() {
console.log("test")
return new School("test");
}
getName() {
return this.name;
}
}
export const Initialise = (
target: any,
key: any,
descriptor: PropertyDescriptor,
) => {
const instance = descriptor.value.apply();
dependencyManager.register(instance.constructor.name, instance);
return descriptor;
};
In here:
targetwill just be the class itself (School)keywill be the function nameinitialisedescriptorwill have thedescriptor.valuewhich points to the original function.- We can call
descriptor.value.apply()to trigger the original function
- We can call
Complete Example
Complete Example of Dependency Injection in typescript using annotation:
dependencyInjection.ts
import "reflect-metadata";
class DependencyManager {
container: Map<string, any>;
constructor() {
this.container = new Map();
}
register(token: string, instance: any): void {
this.container.set(token, instance);
}
resolve<T>(token: string): T {
return this.container.get(token);
}
}
const dependencyManager = new DependencyManager();
export const Service = <T extends { new (...args: any[]): any }>(
constructor: T,
) => {
dependencyManager.register(constructor.name, new constructor());
};
export const Autowired = (target: any, key: string) => {
Object.defineProperty(target, key, {
get: function () {
// Search for className
const instance = dependencyManager.resolve(
Reflect.getMetadata("design:type", target, key).name,
);
if (!instance) {
throw new Error(`Instance ${key} is not autowired`);
}
return instance;
},
});
};
export const Initialise = (
target: any,
key: any,
descriptor: PropertyDescriptor,
) => {
const instance = descriptor.value.apply();
dependencyManager.register(instance.constructor.name, instance);
return descriptor;
};
index.ts
import { Autowired, Initialise, Service } from "./dependencyInjection";
@Service
class Student {
private name: string;
constructor() {
this.name = "some student";
}
getName() {
return this.name;
}
}
class School {
private name: string;
constructor(name: string) {
this.name = name;
}
@Initialise
initialise() {
return new School("test");
}
getName() {
return this.name;
}
}
class MyClass {
@Autowired
private student: Student;
@Autowired
private school: School;
method() {
console.log("my method", this.student.getName());
console.log("school", this.school.getName());
}
}
const myClass = new MyClass();
myClass.method();
Will print out
> [email protected] start
> ts-node index.ts
my method some student
school test
learn/typescript/decorator at master ยท rockmanvnx6/learn (github.com)