Skip to content

Commit

Permalink
feat(di): Support async provider with InjectorService.loadAsync()
Browse files Browse the repository at this point in the history
Closes: #547
  • Loading branch information
Romakita committed Aug 20, 2019
1 parent f3bb044 commit ce9fc8b
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 55 deletions.
1 change: 1 addition & 0 deletions packages/common/test/mvc/models/ControllerProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe("ControllerProvider", () => {
"instance",
"deps",
"useFactory",
"useAsyncFactory",
"useValue"
]);
});
Expand Down
4 changes: 2 additions & 2 deletions packages/common/test/mvc/services/RouteService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe("RouteService", () => {
locals.set(ExpressApplication, expressApp);

await injector.load();
const routeService = await injector.invoke<RouteService>(RouteService, locals, {rebuild: true});
const routeService = injector.invoke<RouteService>(RouteService, locals, {rebuild: true});

// WHEN
routeService.addRoute("/test", MyCtrl);
Expand Down Expand Up @@ -69,7 +69,7 @@ describe("RouteService", () => {
locals.set(ExpressApplication, expressApp);

await injector.load();
const routeService = await injector.invoke<RouteService>(RouteService, locals, {rebuild: true});
const routeService = injector.invoke<RouteService>(RouteService, locals, {rebuild: true});
routeService.addRoute("/test", MyCtrl);

// WHEN
Expand Down
19 changes: 16 additions & 3 deletions packages/di/src/class/Provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,23 @@ export class Provider<T> implements IProvider<T> {
this.useClass = token;
}

get token() {
return this._provide;
}

/**
*
* @returns {any}
*/
get provide(): any {
get provide(): TokenProvider {
return this._provide;
}

/**
*
* @param value
*/
set provide(value: any) {
set provide(value: TokenProvider) {
this._provide = isClass(value) ? getClass(value) : value;
}

Expand Down Expand Up @@ -132,9 +136,18 @@ export class Provider<T> implements IProvider<T> {

/**
* Get the scope of the provider.
*
* ::: tip Note
* Async provider is always a SINGLETON
* :::
*
* @returns {boolean}
*/
get scope(): ProviderScope {
if (this.isAsync()) {
return ProviderScope.SINGLETON;
}

return this._store ? this.store.get("scope") : this._scope;
}

Expand All @@ -155,7 +168,7 @@ export class Provider<T> implements IProvider<T> {
*
*/
clone(): Provider<any> {
const provider = new (getClass(this))(this.provide);
const provider = new (getClass(this))(this.token);

getKeys(this).forEach(key => {
provider[key] = this[key];
Expand Down
84 changes: 55 additions & 29 deletions packages/di/src/services/InjectorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ interface IInvokeSettings {
token: TokenProvider;
parent?: TokenProvider;
scope: ProviderScope;
useScope: boolean;
isBindable: boolean;
deps: any[];

Expand All @@ -49,7 +48,8 @@ interface IInvokeSettings {
*
* // When all services is imported you can load InjectorService.
* const injector = new InjectorService()
* injector.load();
*
* await injector.load();
*
* const myService1 = injector.get<MyService1>(MyServcice1);
* ```
Expand Down Expand Up @@ -173,6 +173,12 @@ export class InjectorService extends Container {
case ProviderScope.SINGLETON:
if (!this.has(token)) {
provider.instance = this.resolve(token, locals, options);

if (provider.isAsync()) {
provider.instance.then((instance: any) => {
provider.instance = instance;
});
}
}

instance = this.get<T>(token)!;
Expand All @@ -193,28 +199,51 @@ export class InjectorService extends Container {
}

/**
* Build all providers from GlobalProviders or from given providers parameters and emit `$onInit` event.
*
* @param container
* Build only providers which are asynchronous.
*/
async load(container: Map<TokenProvider, Provider<any>> = GlobalProviders): Promise<LocalsContainer<any>> {
const locals = new LocalsContainer();
async loadAsync(locals: LocalsContainer<any> = new LocalsContainer()) {
const providers = super.toArray();

// Clone all providers in the container
this.addProviders(container);
for (const provider of providers) {
if (provider.isAsync()) {
await this.invoke(provider.token, locals);
}

if (provider.instance) {
locals.set(provider.token, provider.instance);
}
}

return locals;
}

async loadSync(locals: LocalsContainer<any> = new LocalsContainer()) {
const providers = super.toArray();

for (const provider of providers) {
if (!locals.has(provider.provide) && this.scopeOf(provider) === ProviderScope.SINGLETON) {
this.invoke(provider.provide, locals);
if (!locals.has(provider.token) && this.scopeOf(provider) === ProviderScope.SINGLETON) {
this.invoke(provider.token, locals);
}

if (provider.instance) {
locals.set(provider.provide, provider.instance);
locals.set(provider.token, provider.instance);
}
}

return locals;
}

/**
* Build all providers from given container (or GlobalProviders) and emit `$onInit` event.
*
* @param container
*/
async load(container: Map<TokenProvider, Provider<any>> = GlobalProviders): Promise<LocalsContainer<any>> {
// Clone all providers in the container
this.addProviders(container);

const locals = await this.loadSync(await this.loadAsync());

await locals.emit("$onInit");

return locals;
Expand Down Expand Up @@ -444,40 +473,37 @@ export class InjectorService extends Container {
* @param options
*/
private mapInvokeOptions(token: TokenProvider, options: Partial<IInvokeOptions<any>>): IInvokeSettings {
const {useScope = false} = options;
let deps: TokenProvider[] | undefined = options.deps;
let scope = options.scope;
let construct = (deps: TokenProvider[]) => new token(...deps);
let construct;
let isBindable = false;

if (!token) {
throw new UndefinedTokenError();
}

if (this.hasProvider(token)) {
const provider = this.getProvider(token)!;
const provider = this.hasProvider(token) ? this.getProvider(token)! : new Provider(token);

scope = scope || this.scopeOf(provider);
deps = deps || provider.deps;
scope = scope || this.scopeOf(provider);
deps = deps || provider.deps;

if (provider.useValue) {
construct = () => (isFunction(provider.useValue) ? provider.useValue() : provider.useValue);
} else if (provider.useFactory) {
construct = (deps: TokenProvider[]) => provider.useFactory(...deps);
} else if (provider.useClass) {
isBindable = true;
deps = deps || Metadata.getParamTypes(provider.useClass);
construct = (deps: TokenProvider[]) => new provider.useClass(...deps);
}
if (provider.useValue) {
construct = () => (isFunction(provider.useValue) ? provider.useValue() : provider.useValue);
} else if (provider.useFactory) {
construct = (deps: TokenProvider[]) => provider.useFactory(...deps);
} else if (provider.useAsyncFactory) {
construct = (deps: TokenProvider[]) => provider.useAsyncFactory(...deps);
} else {
deps = deps || Metadata.getParamTypes(token);
// useClass
isBindable = true;
deps = deps || Metadata.getParamTypes(provider.useClass);
construct = (deps: TokenProvider[]) => new provider.useClass(...deps);
}

return {
token,
scope: scope || Store.from(token).get("scope") || ProviderScope.SINGLETON,
deps: deps! || [],
useScope,
isBindable,
construct
};
Expand Down
1 change: 1 addition & 0 deletions packages/di/test/class/Provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe("Provider", () => {
"instance",
"deps",
"useFactory",
"useAsyncFactory",
"useValue"
]);
expect(provider.clone()).to.deep.eq(provider);
Expand Down
2 changes: 1 addition & 1 deletion packages/di/test/integration/interceptor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe("DI Interceptor", () => {

await injector.load();

const serviceTest = await injector.invoke<ServiceTest>(ServiceTest)!;
const serviceTest = injector.invoke<ServiceTest>(ServiceTest)!;

// WHEN
const result = serviceTest.exec("param data");
Expand Down
6 changes: 3 additions & 3 deletions packages/di/test/integration/request.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ describe("DI Request", () => {
const locals = new LocalsContainer();

// WHEN
const result1: any = await injector.invoke(ServiceRequest, locals);
const result2: any = await injector.invoke(ServiceRequest, locals);
const serviceSingleton1: any = await injector.invoke(ServiceSingleton, locals);
const result1: any = injector.invoke(ServiceRequest, locals);
const result2: any = injector.invoke(ServiceRequest, locals);
const serviceSingleton1: any = injector.invoke(ServiceSingleton, locals);
const serviceSingleton2: any = injector.get(ServiceSingleton);

Sinon.stub(result1, "$onDestroy");
Expand Down
Loading

0 comments on commit ce9fc8b

Please sign in to comment.