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:
target
will just be the class itself (School
)key
will be the function nameinitialise
descriptor
will have thedescriptor.value
which 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)