Skip to content

Commit f6762f1

Browse files
Merge pull request #89 from thohoh/#84_lifetime_for_factories
#84 lifetime for factories
2 parents f864b64 + 0ce9e50 commit f6762f1

File tree

6 files changed

+213
-83
lines changed

6 files changed

+213
-83
lines changed

README.md

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,35 +104,66 @@ 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 using **useClass** and **useFactory**.
108+
Default life time for all items in a container can be set by passing an option object to it's contructor with **defailtLifeTime** attribute. Possible values: **LifeTime.PerRequest** (resolves instances) and **LifeTime.Persistent** (resolves singletons);
108109

109110
```typescript
110111
import { LifeTime } from 'container-ioc';
111112

113+
const container = new Container({
114+
defaultLifeTime: LifeTime.PerRequest
115+
});
116+
```
117+
> You can also specify life time individually for each item in a container by specifying **lifeTime** attribute.
118+
119+
```typescript
112120
container.register([
113-
{ token: TService, useClass: Service, lifeTime: LifeTime.PerRequest }
121+
{
122+
token: TService,
123+
useClass: Service,
124+
lifeTime: LifeTime.PerRequest
125+
}
114126
]);
115127
```
116-
117-
### Hierarchical containers.
118-
> If container can't find value, it will look it up in ascendant containers.
119128
```typescript
129+
container.register([
130+
{
131+
token: TService,
132+
useFactory: () => {
133+
return {
134+
serve(): void {}
135+
}
136+
},
137+
lifeTime: LifeTime.Persistent
138+
}
139+
]);
140+
```
120141

121-
let parentContainer = new Container();
122-
let childContainer = parentContainer.createChild();
123-
124-
parentContainer.register({ token: TApplication, useClass: Application });
142+
### Hierarchical containers.
143+
> If a container can't find a value within itself, it will look it up in ascendant containers. There a 3 ways to set a parent for a container.
125144
126-
childContainer.resolve(TApplication);
145+
###### 1. Container.createChild() method.
146+
```typescript
147+
const parentContainer = new Container();
148+
const childContainer = parentContainer.createChild();
127149
```
128-
> You can also assign parent container to any other container
150+
151+
###### 2. Container.setParent() method.
129152
```typescript
130-
let parent = new Container();
131-
let child = new Container();
153+
const parent = new Container();
154+
const child = new Container();
132155

133156
child.setParent(parent);
134157
```
135158

159+
###### 3. Via Container's constructor with options.
160+
```typescript
161+
const parent = new Container();
162+
const child = new Container({
163+
parent: parent
164+
});
165+
```
166+
136167
### Using Factories
137168
```typescript
138169
/* Without injections */

src/lib/container.interface.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { IInjectionInstance, ProviderToken, RegistrationProvider } from './interfaces';
1+
import { IInjectionInstance, ProviderToken, RegistrationProvider, LifeTime } from './interfaces';
2+
3+
export interface IContainerOptions {
4+
parent?: IContainer;
5+
defaultLifeTime?: LifeTime;
6+
}
27

38
export interface IContainer {
49
register(provider: RegistrationProvider|RegistrationProvider[]): void;

src/lib/container.ts

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
import { IConstructor, IInjectionInstance, IInjectionMd, IProvider, LifeTime, ProviderToken, RegistrationProvider } from './interfaces';
2-
import { IRegistryData, RegistryData } from './registry-data';
3-
import { IContainer } from './container.interface';
4-
import { ClassNotInjectableError, InvalidProviderProvidedError, NoProviderError } from './exceptions';
2+
import { FactoryFunction, IFactory, IRegistryData, RegistryData } from './registry-data';
3+
import { IContainer, IContainerOptions } from './container.interface';
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';
88

99
const MetadataAnnotator: IMetadataAnnotator = AnnotatorProvider.get();
1010

1111
export class Container implements IContainer {
12+
private static DEFAULT_LIFE_TIME = LifeTime.Persistent;
1213
private registry: Map<ProviderToken, IRegistryData> = new Map();
14+
private parent: IContainer;
15+
private defaultLifeTime: LifeTime = Container.DEFAULT_LIFE_TIME;
1316

14-
constructor(private parent?: IContainer) {}
17+
constructor(options?: IContainerOptions) {
18+
if (options) {
19+
this.parent = <IContainer> options.parent;
20+
this.defaultLifeTime = options.defaultLifeTime || this.defaultLifeTime;
21+
}
22+
}
1523

1624
public register(provider: RegistrationProvider|RegistrationProvider[]): void {
1725
provider = this.nornalizeProvider(provider);
@@ -29,15 +37,15 @@ export class Container implements IContainer {
2937
}
3038

3139
public createScope(): IContainer {
32-
return new Container(this);
40+
return new Container({ parent: this });
3341
}
3442

35-
public setParent(parent: IContainer): void {
36-
this.parent = parent;
43+
public createChild(): IContainer {
44+
return this.createScope();
3745
}
3846

39-
public createChild(): IContainer {
40-
return new Container(this);
47+
public setParent(parent: IContainer): void {
48+
this.parent = parent;
4149
}
4250

4351
private resolveInternal(token: ProviderToken, traceMessage?: string): IInjectionInstance {
@@ -56,24 +64,7 @@ export class Container implements IContainer {
5664
return registryData.instance;
5765
}
5866

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);
67+
const instance: IInjectionInstance = this.instantiateWithFactory(registryData.factory, traceMessage);
7768

7869
if (registryData.lifeTime === LifeTime.Persistent) {
7970
registryData.instance = instance;
@@ -92,28 +83,57 @@ export class Container implements IContainer {
9283

9384
if (provider.useValue) {
9485
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-
}
86+
} else {
87+
const factoryValue = provider.useFactory || provider.useClass;
88+
const isClass: boolean = this.isInjectable(factoryValue);
89+
90+
registryData.factory = {
91+
value: factoryValue,
92+
isClass
93+
};
94+
95+
if (isClass) {
96+
registryData.factory.inject = this.retrieveInjectionsFromClass(<IConstructor> registryData.factory.value);
97+
} else {
98+
registryData.factory.inject = this.convertTokensToInjectionMd(<ProviderToken> provider.inject);
99+
}
101100

102-
registryData.lifeTime = provider.lifeTime || LifeTime.Persistent;
101+
registryData.lifeTime = provider.lifeTime || this.defaultLifeTime;
102+
}
103103

104104
this.registry.set(provider.token, registryData);
105105
}
106106

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));
107+
private convertTokensToInjectionMd(tokens: ProviderToken[]): IInjectionMd[] {
108+
let injections: IInjectionMd[] = [];
109+
110+
if (tokens) {
111+
injections = tokens.map((token: ProviderToken, index: number) => {
112+
return {
113+
token,
114+
parameterIndex: index
115+
};
116+
});
117+
}
118+
119+
return injections;
120+
}
121+
122+
private instantiateWithFactory(factory: IFactory, traceMessage: string): IInjectionInstance {
123+
const injections = <IInjectionMd[]> factory.inject;
124+
125+
const resolvedInjections: any[] = injections.map(injection => this.resolveInternal(injection.token, traceMessage));
110126

111127
const args: any[] = [];
112-
injectionsMd.forEach((injection: IInjectionMd, index) => {
128+
injections.forEach((injection: IInjectionMd, index: number) => {
113129
args[injection.parameterIndex] = resolvedInjections[index];
114130
});
115131

116-
return new cls(...args);
132+
if (factory.isClass) {
133+
return new (<IConstructor> factory.value)(...args);
134+
} else {
135+
return (<FactoryFunction> factory.value)(...args);
136+
}
117137
}
118138

119139
private nornalizeProvider(provider: RegistrationProvider|RegistrationProvider[]): IProvider {
@@ -167,7 +187,8 @@ export class Container implements IContainer {
167187
return !!(MetadataAnnotator.getMetadata(INJECTABLE_MD_KEY, cls));
168188
}
169189

170-
private getInjections(cls: any): IInjectionMd[] {
171-
return MetadataAnnotator.getMetadata(INJECTIONS_MD_KEY, cls) || [];
190+
private retrieveInjectionsFromClass(cls: IConstructor): IInjectionMd[] {
191+
const injections: IInjectionMd[] = MetadataAnnotator.getMetadata(INJECTIONS_MD_KEY, cls) || [];
192+
return this.convertTokensToInjectionMd(injections);
172193
}
173194
}

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
}

0 commit comments

Comments
 (0)