A progressive Node.js tools for building efficient and scalable applications.
Dependency Injection (DI) is a design pattern used to implement IoC. It allows the creation of dependent objects outside of a class and provides those objects to a class through different ways. Using DI, we move the creation and binding of the dependent objects outside of the class that depends on them.
Installing
npm i @typeix/di --save
Decorator | Info |
---|---|
@Injectable() |
specifies that Typeix can use this class in the DI system. decorators can be used on classes only. |
@Inject() |
specifies DI system, which object to provide when object is created, if there is no provider defined DI will throw error with explanation. |
@AfterConstruct() |
specifies DI system, what to execute immediately after object is created. |
@CreateProvider() |
decorator specifies DI system, to create new provider on injection point, this will create new object even if there is a provider defined in parent Injector. |
An interceptor pattern is a software design pattern that is used when software systems or frameworks want to offer a way to change, or augment, their usual processing cycle.
import {Injectable, Injector, Inject, createMethodInterceptor} from "@typeix/di";
@Injectable()
class LoggerInterceptor implements Interceptor {
@Inject() logger: Logger;
async invoke(method: Method): Promise<any> {
const result = await method.invoke();
this.logger.info(method.args.message, result);
return result;
}
}
export function Logger(message) {
return createMethodInterceptor(Logger, LoggerInterceptor, {message});
};
@Injectable()
class ChildService {
@Logger("Logging ChildService.getName result: ")
getName(): string {
return this.name;
}
}
In order to create an object of some class you need to define class as @Injectable and injection points via @Inject, then when creating an instance you need to provide DI what to construct and which providers to create.
import {Injectable, Injector, Inject} from "@typeix/di";
@Injectable()
class NameService {
@Inject("name") private name: string;
getName() {
return this.name;
}
}
@Injectable()
class ChildService {
@Inject() nameService: NameService;
getName(): string {
return this.nameService.getName();
}
}
Injector.createAndResolve(ChildService, [
{provide: "name", useValue: "Igor"},
NameService
]).then(injector => {
let service = injector.get(ChildService);
return service.getName();
});
There are three type of providers class, value and factory provider, functions inside providers array are converted to class providers.
There are three types of providers in total:
{provde, useClass}
is a class provider.
{provde, useFactory}
is a factory provider.
{provde, useValue}
is a value provider.
Dependent providers?: Array<Function | IProvider>
inside of provider
(class and factory) are extra dependencies that are created before provider and delivered as dependencies to provider
itself.
interface IProvider {
provide: any;
useValue?: any;
useClass?: Function;
useFactory?: Function;
providers?: Array<Function | IProvider>;
}
NOTE:
By default all providers are immutable, however you can define mutable provider keys.
If provider is not mutable, and you are trying to create new instance of same provider on current Injector instance,
injector will throw error.
Injector.getProviders(provider: IProvider, propertyKey?: string)
will return all providers which needs to be injected,
if propertyKey is not defined injector will return providers for constructor.
Injector.getAllMetadataForTarget(provider: IProvider)
will return all metadata, injector cache them internally. Works
with class provider only.
class Injector {
constructor(_parent?: Injector, keys?: Array<any>);
static getProviders(provider: IProvider, propertyKey?: string): Array<IProvider>;
// returns all metadata for provider
static getAllMetadataForTarget(provider: IProvider): Array<IMetadata>;
// creates new child injector, provider and providers
static createAndResolveChild(parent: Injector, Class: Function | IProvider, providers: Array<MixedProvider>): Promise<Injector>;
// creates new injector, provider and providers
static createAndResolve(Class: Function | IProvider, providers: Array<MixedProvider>): Promise<Injector>;
// creates new provider and providers
createAndResolve(provider: IProvider, providers: Array<IProvider>): Promise<any>;
// clens current injectables and all child injectors & providers
destroy(): void;
// check if providier exists on current SyncInjector instance
has(key: any): boolean;
// get provider value from current SyncInjector if not found bubble parrent's
// if not found on any of parents exception is thrown.
get(provider: string, Class?: IProvider): any;
get<T>(provider: Type<T>, Class?: IProvider): T;
// set provider and value
set(key: any, value: Object): void;
getParent(): Injector;
setParent(injector: Injector): void;
setName(provider: IProvider): void;
hasChild(injector: Injector): boolean;
addChild(injector: Injector): this;
}
Since version 8.x default Injector behavior is converted to async API, if you want to use sync api you need to use
Injector.Sync.createAndResolve
or Injector.Sync.createAndResolveChild
difference is that Async API supports
Async providers, it's allowed to use of async/await in factory and return Promises in value provider!
class SyncInjector {
constructor(_parent?: Injector, keys?: Array<any>);
static getProviders(provider: IProvider, propertyKey?: string): Array<IProvider>;
// returns all metadata for provider
static getAllMetadataForTarget(provider: IProvider): Array<IMetadata>;
// creates new child injector, provider and providers
static createAndResolveChild(parent: Injector, Class: Function | IProvider, providers: Array<MixedProvider>): Injector;
// creates new injector, provider and providers
static createAndResolve(Class: Function | IProvider, providers: Array<MixedProvider>): Injector;
// creates new provider and providers
createAndResolve(provider: IProvider, providers: Array<IProvider>): any;
// clens current injectables and all child injectors & providers
destroy(): void;
// check if providier exists on current SyncInjector instance
has(key: any): boolean;
// get provider value from current SyncInjector if not found bubble parrent's
// if not found on any of parents exception is thrown.
get(provider: string, Class?: IProvider): any;
get<T>(provider: Type<T>, Class?: IProvider): T;
// set provider and value
set(key: any, value: Object): void;
getParent(): Injector;
setParent(injector: Injector): void;
setName(provider: IProvider): void;
hasChild(injector: Injector): boolean;
addChild(injector: Injector): this;
}