UNPKG

ts.di

Version:

Typescript写的依赖注入库。An dependency-injection library for TypeScript.

184 lines (160 loc) 6.79 kB
import "reflect-metadata"; export interface Constructor<T extends Object> extends Function { new(...args): T; readonly constructor: Function; readonly prototype: Object; } // deprecated!, use Constructor instead. export type Newable<T> = Constructor<T>; @Injectable() export class Injector { static get<T>(ctor: Constructor<T>): T { return Injector.getWithFollower(ctor, undefined); } /* Set a class as @Singleton() and give it a custom \a value for future retrieving. * If there exist an instance of target class, and \a overwrite haven't set, * setting will be failed and function will return false. Otherwise, newIt singleton * instance will be set and true will be returned. */ static setSingleton<T>(ctor: Constructor<T>, val: T, overwrite?: boolean): boolean { Injector.registerSingleton(ctor); if (!overwrite && ctor[Injector.singletonInstance]) { return false; } ctor[Injector.singletonInstance] = val; return true; } static isSingletonExist<T>(ctor: Constructor<T>): boolean { return !!ctor[Injector.singletonInstance]; } private static getWithFollower<T>(ctor: Constructor<T>, fllower?: any[]): T { // A class without @Injectable() decorated can be instantiate too // **But that is not recommended** if (Injector.strictDecorationCheck && !ctor[Injector.isInjectable]) { throw new Error("To get a instance of " + ctor.name + ", you should decorate it with @Injectable().") } // There is a different instantiation for Singleton class. if (ctor[Injector.isSingleton]) { return ctor[Injector.singletonInstance] = ctor[Injector.singletonInstance] || Injector.create(ctor); } return Injector.create(ctor, fllower); } private static create<T>(ctor: Constructor<T>, follwer?: Function[]): T { // If there is a factory for it if (ctor[Injector.factorSymbol]) return ctor[Injector.factorSymbol](); let argtypes = Reflect.getMetadata("design:paramtypes", ctor) || []; // If it depends on nothing, just newIt it. if (argtypes.length === 0) { return new ctor(); } // If it depends on something else, check if there is a dependency loop let newFollower; if (Injector.checkDependencyLoop) { if (follwer && follwer.indexOf(ctor) != -1) { throw new Error("Found dependency loop while trying to instantiate " + ctor.name + ". Please check your code."); } // Prepare for next dependency loop check newFollower = (<Function[]>[ctor]).concat(follwer); } // Instantiate the arguments it's constructor needs let args = []; for (let i = 0; i < argtypes.length; i++) { args.push(Injector.getWithFollower(argtypes[i], newFollower)); } // newIt it return new ctor(...args); } static registerInjectable<T>(ctor: Constructor<T>): Constructor<T> { ctor[Injector.isInjectable] = true; return ctor; } static registerSingleton<T>(ctor: Constructor<T>): Constructor<T> { ctor[Injector.isInjectable] = true; ctor[Injector.isSingleton] = true; return ctor; } static injectDecoratorFactor(undefOrTypeFunc?: {<T>(): Constructor<T>}) { if (undefOrTypeFunc) { return (target, propertyKey) => { Object.defineProperty(target, propertyKey, { get: () => { let obj = Injector.get(undefOrTypeFunc()); Object.defineProperty(target, propertyKey, obj); return obj; } }) }; } return (target, propertyKey) => { Object.defineProperty(target, propertyKey, { get: () => { let obj = Injector.get(Reflect.getMetadata("design:type", target, propertyKey)); Object.defineProperty(target, propertyKey, obj); return obj; } }) } } static factorOfDecoratorFactor<T>(ctor: Constructor<T>): MethodDecorator { return <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>): TypedPropertyDescriptor<T> | void => { ctor[Injector.factorSymbol] = target[propertyKey]; } } private static factorSymbol = Symbol('Injector.factorSymbol'); private static isInjectable = Symbol('Injector.isInjectable'); private static isSingleton = Symbol('Injector.isSingleton'); private static singletonInstance = Symbol('Injector.singletonInstance'); public static checkDependencyLoop = false; // for performance considering, disable it by default. public static strictDecorationCheck = true; } /* \brief @Injectable() marks a class injectable * * Use Injector.get() to get a instance of that class * Calling Injector.get() multiple times on a @Injectable() decorated class * will produce different instances. * */ export function Injectable(): ClassDecorator { return Injector.registerInjectable; } /* @Singleton() marks a singleton class. * Use Injector.get() to get the instance of singleton class */ export function Singleton(): ClassDecorator { return Injector.registerSingleton; } /* \brief @Inject() marks a property as injected property * * Unlike @Injectable() unable to instantiate class having dependency loop, * @Inject(()=>Type) can instantiate dependencies like A->B->A as long as there is a * @Singleton() class among them. * */ export function Inject(TypeFunc?: {<T>(_?: any): Constructor<T>}): PropertyDecorator { return Injector.injectDecoratorFactor(TypeFunc); } /* \brief @FactorOf(Class) tell Injector to use it(this decorated function) to create instance of \a Class. * * Example : * \code ts * @Singleton() * class A { * // it's almost impossible to create A using our Injector. * // Because param val should provided by ourselves but not Injector. * // So a factor function to create it is necessary. * constructor(private val:number){} * } * class FactorUtils { * @FactorOf(A) * someFunctionToCreateA(){ * return {val:1234} * } * } * * Injector.get(A).val === 1234;// this will be true * \endcode */ export function FactorOf<T>(ctor: Constructor<T>): MethodDecorator { return Injector.factorOfDecoratorFactor(ctor); }