Dependency injection is usually what we call ioc mode. Today we share the ioc mode implemented by typescript language. The main component used here is reflection-metadata, which can acquire or set metadata information. Its function is to get the original data and then create reflections similar to those in C#. First, look at the first. Section code:
import "reflect-metadata"; /** * object manager */ const _partialContainer = new Map<string, any>(); const PARAMTYPES = "design:paramtypes";//There are many choices for raw data to be reflected. What we choose here is to get the parameter type of the constructor for subsequent judgment. /** * Local injector, which injects global services and instances are shared globally */ export function Inject(): ClassDecorator { return target => { const params: Array<any> = Reflect.getMetadata(PARAMTYPES, target); if (params) for (const item of params) { if (item === target) throw new Error("Can't inject oneself"); } _partialContainer.set(target.name, target);//Join the Object Manager, at which point the object has not yet been created } }
The above code is to create a class-level decorator, which means that all classes using this decorator will be managed by the Dependency Injection Object Manager. There is no service created immediately because reflection-metadata has the highest execution opportunity and this Dependency Injection supports manual injection of some instance objects. In order to prevent undefined injection parameters from appearing, the creation of instances is left behind. See the following code:
/** * * @param type Created instance objects */ export function addServiceInGlobal(...types: Array<Object>) { for (const iterator of types) { _partialContainer.set(iterator.constructor.name, iterator); } }
The above method is called when the instance object is injected manually. We need to improve the execution priority of this method. Specific examples will be demonstrated later. Next is the most important part. Create an instance part:
export function serviceProvider<T>(service: ServiceType<T>): T { if (_partialContainer.has(service.name) && !_partialContainer.get(service.name).name) return _partialContainer.get(service.name);// If the instance has been created, it returns directly const params: Array<any> = Reflect.getMetadata(PARAMTYPES, service);// Reflections get the parameter type of the constructor const constrparams = params.map(item => { // Dependencies in instantiated parameters if (!_partialContainer.has(item.name)) throw new Error(`${item}Not injected`);// Throw an exception if there is no injection if (item.length)// Indicates that this type has other dependencies return serviceProvider(item);// Recursively continue to acquire other dependencies if (_partialContainer.has(item.name) && !_partialContainer.get(item.name).name) return _partialContainer.get(item.name);// If the instance has been created, it returns directly const obj = new item();// There is no other dependency to start creating instances _partialContainer.set(item.name, obj);// Replace objects that were not instantiated in Object Manager return obj; }); const obj = new service(...constrparams); // This means that the object has not been created and the object has been created. _partialContainer.set(service.name, obj);// Replace objects that were not instantiated in Object Manager return obj; }
The above code is a bit complicated, and it's not difficult to understand anything else. In plain English, if you have instantiated the direct return instance, you can start the object and create all the dependencies. Next is an example:
import { serviceProvider, addServiceInGlobal, Inject } from './core/injectable/injector'; import "reflect-metadata"; import moment = require('moment'); @Inject() export class ServiceA{ property?:string; msg(){ return "ServiceA"; } } @Inject() export class ServiceC { constructor(private service: ServiceA) { } print() { console.log( this.service.property); return "Called me"; } } @Inject() export class ServiceD{ print(){ console.log("I'm Testing Injection"); } } @Inject() export class GlobalService { constructor(private service: ServiceC) { } msg!: string; print() { console.log(`Sharing module ${this.service.print()}`) } } @Inject() export class Init { constructor(private service: ServiceA, private serviceD: ServiceD, private global: GlobalService, private date: Date, private strList: string[], private serviceC: ServiceC, ) { } start() { console.log(this.service.msg()); this.service.property = "A Shared data for module settings" console.log(moment(this.date).format("YYYY-MM-DD")) console.log(this.strList); this.serviceD.print(); this.serviceC.print(); this.global.print(); } } const obj = new Date("2017-1-1"); const str = ['Lu Shunbin','Green hand','peas','Big iron','CC Brother','A group of people in the yard farmer's house']; addServiceInGlobal(obj, str); // Add manually created instance objects to the object manager const service = serviceProvider(Init); // Start creating instances service.start()// implement
In the example above, we get the following execution results:
Summary: I did not do the default global injection.Singleton (singleton) can be implemented by modifying the code slightly if it is to be done. The difficulty here may be a reflection-based design method. If the front-end thinking is slightly more difficult to understand, the back-end thinking is slightly better.