Skip to content

Commit 7622be8

Browse files
#84_LifeTime for Factories
1 parent f864b64 commit 7622be8

File tree

5 files changed

+111
-65
lines changed

5 files changed

+111
-65
lines changed

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class Service {
104104
```
105105

106106
### Life Time control.
107-
> By default, containers resolve singletons when registering with **useClass**. Change it by setting **lifeTime** attribute to **LifeTime.PerRequest**.
107+
> By default, containers resolve singletons when **useClass** and **useFactory**. Change it by setting **lifeTime** attribute to **LifeTime.PerRequest**.
108108
109109
```typescript
110110
import { LifeTime } from 'container-ioc';
@@ -113,6 +113,14 @@ container.register([
113113
{ token: TService, useClass: Service, lifeTime: LifeTime.PerRequest }
114114
]);
115115
```
116+
```typescript
117+
container.register([
118+
{
119+
token: TService,
120+
useFactory: () => { serve(): void {} },
121+
lifeTime: LifeTime.PerRequest }
122+
]);
123+
```
116124

117125
### Hierarchical containers.
118126
> If container can't find value, it will look it up in ascendant containers.

src/lib/container.ts

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { IConstructor, IInjectionInstance, IInjectionMd, IProvider, LifeTime, ProviderToken, RegistrationProvider } from './interfaces';
2-
import { IRegistryData, RegistryData } from './registry-data';
2+
import { FactoryFunction, IFactory, IRegistryData, RegistryData } from './registry-data';
33
import { IContainer } from './container.interface';
4-
import { ClassNotInjectableError, InvalidProviderProvidedError, NoProviderError } from './exceptions';
4+
import { InvalidProviderProvidedError, NoProviderError } from './exceptions';
55
import { INJECTABLE_MD_KEY, INJECTIONS_MD_KEY } from './metadata/keys';
66
import { IMetadataAnnotator } from './metadata/metadata-annotator.interface';
77
import { AnnotatorProvider } from './metadata/index';
@@ -32,14 +32,14 @@ export class Container implements IContainer {
3232
return new Container(this);
3333
}
3434

35-
public setParent(parent: IContainer): void {
36-
this.parent = parent;
37-
}
38-
3935
public createChild(): IContainer {
4036
return new Container(this);
4137
}
4238

39+
public setParent(parent: IContainer): void {
40+
this.parent = parent;
41+
}
42+
4343
private resolveInternal(token: ProviderToken, traceMessage?: string): IInjectionInstance {
4444
traceMessage = this.buildTraceMessage(token, traceMessage);
4545

@@ -56,24 +56,7 @@ export class Container implements IContainer {
5656
return registryData.instance;
5757
}
5858

59-
if (registryData.factory) {
60-
let injections: ProviderToken[] = [];
61-
62-
if (registryData.injections) {
63-
injections = registryData.injections.map(i => this.resolveInternal(i, traceMessage));
64-
}
65-
66-
return registryData.factory(...injections);
67-
}
68-
69-
const constructor: IConstructor = registryData.cls;
70-
71-
const isInjectable: boolean = this.isInjectable(constructor);
72-
if (!isInjectable) {
73-
throw new ClassNotInjectableError(constructor.name);
74-
}
75-
76-
const instance: IInjectionInstance = this.createInstance(constructor, traceMessage);
59+
const instance: IInjectionInstance = this.instantiateWithFactory(registryData.factory, traceMessage);
7760

7861
if (registryData.lifeTime === LifeTime.Persistent) {
7962
registryData.instance = instance;
@@ -92,28 +75,57 @@ export class Container implements IContainer {
9275

9376
if (provider.useValue) {
9477
registryData.instance = provider.useValue;
95-
} else if (provider.useClass) {
96-
registryData.cls = provider.useClass;
97-
} else if (provider.useFactory) {
98-
registryData.factory = provider.useFactory;
99-
registryData.injections = <ProviderToken[]> provider.inject;
100-
}
78+
} else {
79+
const factoryValue = provider.useFactory || provider.useClass;
80+
const isClass: boolean = this.isInjectable(factoryValue);
81+
82+
registryData.factory = {
83+
value: factoryValue,
84+
isClass
85+
};
86+
87+
if (isClass) {
88+
registryData.factory.inject = this.retrieveInjectionsFromClass(<IConstructor> registryData.factory.value);
89+
} else {
90+
registryData.factory.inject = this.convertTokensToInjectionMd(<ProviderToken> provider.inject);
91+
}
10192

102-
registryData.lifeTime = provider.lifeTime || LifeTime.Persistent;
93+
registryData.lifeTime = provider.lifeTime || LifeTime.Persistent; // TODO issue #88
94+
}
10395

10496
this.registry.set(provider.token, registryData);
10597
}
10698

107-
private createInstance(cls: IConstructor, message: string): IInjectionInstance {
108-
const injectionsMd: IInjectionMd[] = this.getInjections(cls);
109-
const resolvedInjections: any[] = injectionsMd.map(injectionMd => this.resolveInternal(injectionMd.token, message));
99+
private convertTokensToInjectionMd(tokens: ProviderToken[]): IInjectionMd[] {
100+
let injections: IInjectionMd[] = [];
101+
102+
if (tokens) {
103+
injections = tokens.map((token: ProviderToken, index: number) => {
104+
return {
105+
token,
106+
parameterIndex: index
107+
};
108+
});
109+
}
110+
111+
return injections;
112+
}
113+
114+
private instantiateWithFactory(factory: IFactory, traceMessage: string): IInjectionInstance {
115+
const injections = <IInjectionMd[]> factory.inject;
116+
117+
const resolvedInjections: any[] = injections.map(injection => this.resolveInternal(injection.token, traceMessage));
110118

111119
const args: any[] = [];
112-
injectionsMd.forEach((injection: IInjectionMd, index) => {
120+
injections.forEach((injection: IInjectionMd, index: number) => {
113121
args[injection.parameterIndex] = resolvedInjections[index];
114122
});
115123

116-
return new cls(...args);
124+
if (factory.isClass) {
125+
return new (<IConstructor> factory.value)(...args);
126+
} else {
127+
return (<FactoryFunction> factory.value)(...args);
128+
}
117129
}
118130

119131
private nornalizeProvider(provider: RegistrationProvider|RegistrationProvider[]): IProvider {
@@ -167,7 +179,8 @@ export class Container implements IContainer {
167179
return !!(MetadataAnnotator.getMetadata(INJECTABLE_MD_KEY, cls));
168180
}
169181

170-
private getInjections(cls: any): IInjectionMd[] {
171-
return MetadataAnnotator.getMetadata(INJECTIONS_MD_KEY, cls) || [];
182+
private retrieveInjectionsFromClass(cls: IConstructor): IInjectionMd[] {
183+
const injections: IInjectionMd[] = MetadataAnnotator.getMetadata(INJECTIONS_MD_KEY, cls) || [];
184+
return this.convertTokensToInjectionMd(injections);
172185
}
173186
}

src/lib/decorators.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,7 @@ export function Injectable(injections?: ProviderToken[]) {
1111

1212
if (injections && Array.isArray(injections)) {
1313
const injectionMd: IInjectionMd[] = MetadataAnnotator.getMetadata(INJECTIONS_MD_KEY, target) || [];
14-
15-
injections.forEach((injectionToken, injectionIndex) => {
16-
injectionMd.push({
17-
token: injectionToken,
18-
parameterIndex: injectionIndex
19-
});
20-
});
21-
14+
injections.forEach(token => injectionMd.push(token));
2215
MetadataAnnotator.defineMetadata(INJECTIONS_MD_KEY, injectionMd, target);
2316
}
2417
};
@@ -27,12 +20,7 @@ export function Injectable(injections?: ProviderToken[]) {
2720
export function Inject(token: any) {
2821
return (target: object, propertyKey: string | symbol, parameterIndex: number) => {
2922
const injections: IInjectionMd[] = MetadataAnnotator.getMetadata(INJECTIONS_MD_KEY, target) || [];
30-
31-
injections.push({
32-
token,
33-
parameterIndex
34-
});
35-
23+
injections.push(token);
3624
MetadataAnnotator.defineMetadata(INJECTIONS_MD_KEY, injections, target);
3725
};
3826
}

src/lib/registry-data.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
import { IConstructor, IInjectionInstance, LifeTime, ProviderToken } from './interfaces';
1+
import { IConstructor, IInjectionInstance, IInjectionMd, LifeTime } from './interfaces';
2+
3+
export type FactoryFunction = (...args: any[]) => any;
4+
5+
export interface IFactory {
6+
value: IConstructor | FactoryFunction;
7+
isClass: boolean;
8+
inject?: IInjectionMd[];
9+
}
210

311
export interface IRegistryData {
412
instance: IInjectionInstance;
5-
cls: IConstructor;
6-
factory: (...args: any[]) => any;
7-
injections: ProviderToken[];
13+
factory: IFactory;
814
lifeTime: LifeTime;
915
}
1016

1117
export class RegistryData {
1218
public instance: IInjectionInstance;
13-
public cls: IConstructor;
14-
public factory: (...args: any[]) => any;
15-
public injections: ProviderToken[];
19+
public factory: IFactory;
1620
public lifeTime: LifeTime;
1721
}

src/tests/container.spec.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ describe('Container', () => {
9191
expect(instance instanceof TestClass).to.be.true;
9292
});
9393

94-
it('should resolve ф value when registered with "useFactory"', () => {
94+
it('should resolve value when registered with "useFactory"', () => {
9595
container.register({
9696
token: 'V',
9797
useFactory: () => {
@@ -144,7 +144,7 @@ describe('Container', () => {
144144
});
145145

146146
describe('LifeTime', () => {
147-
it('should resolve a singleton instance if LifeTime was not specified', () => {
147+
it('should resolve a singleton by default if LifeTime was not specified with useClass', () => {
148148
@Injectable()
149149
class A {}
150150

@@ -156,7 +156,40 @@ describe('Container', () => {
156156
expect(instance1).to.be.equal(instance2);
157157
});
158158

159-
it('should resolve a different instances if LifeTime was set to LifeTime.PerRequest', () => {
159+
it('should resolve a singleton by default if LifeTime was not specified with useFactory', () => {
160+
class A {}
161+
162+
container.register([
163+
{
164+
token: A,
165+
useFactory: () => new A()
166+
}
167+
]);
168+
169+
const instance1 = container.resolve(A);
170+
const instance2 = container.resolve(A);
171+
172+
expect(instance1).to.be.equal(instance2);
173+
});
174+
175+
it('should resolve an instance with useFactory if LifeTime was set to LifeTime.PerRequest', () => {
176+
class A {}
177+
178+
container.register([
179+
{
180+
token: A,
181+
useFactory: () => new A(),
182+
lifeTime: LifeTime.PerRequest
183+
}
184+
]);
185+
186+
const instance1 = container.resolve(A);
187+
const instance2 = container.resolve(A);
188+
189+
expect(instance1).not.to.be.equal(instance2);
190+
});
191+
192+
it('should resolve an instances if LifeTime was set to LifeTime.PerRequest', () => {
160193
@Injectable()
161194
class A {}
162195

@@ -168,7 +201,7 @@ describe('Container', () => {
168201
expect(instance1).not.to.be.equal(instance2);
169202
});
170203

171-
it('should resolve a different instances if LifeTime was set to LifeTime.PerRequest in case of nested dependencies', () => {
204+
it('should resolve different instances if LifeTime was set to LifeTime.PerRequest in case of nested dependencies', () => {
172205
@Injectable()
173206
class A {
174207
constructor(@Inject('IB') private b: any) {}
@@ -308,7 +341,7 @@ describe('Container', () => {
308341

309342
describe('createScope()', () => {
310343
it('should create child scope', () => {
311-
const childContainer: any = container.createScope();
344+
const childContainer: any = container.createChild();
312345
expect(childContainer).to.be.ok;
313346
expect(childContainer.parent).to.equal(container);
314347
});

0 commit comments

Comments
 (0)