Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,35 +104,66 @@ class Service {
```

### Life Time control.
> By default, containers resolve singletons when registering with **useClass**. Change it by setting **lifeTime** attribute to **LifeTime.PerRequest**.
> By default, containers resolve singletons when using **useClass** and **useFactory**.
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);

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

const container = new Container({
defaultLifeTime: LifeTime.PerRequest
});
```
> You can also specify life time individually for each item in a container by specifying **lifeTime** attribute.

```typescript
container.register([
{ token: TService, useClass: Service, lifeTime: LifeTime.PerRequest }
{
token: TService,
useClass: Service,
lifeTime: LifeTime.PerRequest
}
]);
```

### Hierarchical containers.
> If container can't find value, it will look it up in ascendant containers.
```typescript
container.register([
{
token: TService,
useFactory: () => {
return {
serve(): void {}
}
},
lifeTime: LifeTime.Persistent
}
]);
```

let parentContainer = new Container();
let childContainer = parentContainer.createChild();

parentContainer.register({ token: TApplication, useClass: Application });
### Hierarchical containers.
> 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.

childContainer.resolve(TApplication);
###### 1. Container.createChild() method.
```typescript
const parentContainer = new Container();
const childContainer = parentContainer.createChild();
```
> You can also assign parent container to any other container

###### 2. Container.setParent() method.
```typescript
let parent = new Container();
let child = new Container();
const parent = new Container();
const child = new Container();

child.setParent(parent);
```

###### 3. Via Container's constructor with options.
```typescript
const parent = new Container();
const child = new Container({
parent: parent
});
```

### Using Factories
```typescript
/* Without injections */
Expand Down
7 changes: 6 additions & 1 deletion src/lib/container.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { IInjectionInstance, ProviderToken, RegistrationProvider } from './interfaces';
import { IInjectionInstance, ProviderToken, RegistrationProvider, LifeTime } from './interfaces';

export interface IContainerOptions {
parent?: IContainer;
defaultLifeTime?: LifeTime;
}

export interface IContainer {
register(provider: RegistrationProvider|RegistrationProvider[]): void;
Expand Down
103 changes: 62 additions & 41 deletions src/lib/container.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { IConstructor, IInjectionInstance, IInjectionMd, IProvider, LifeTime, ProviderToken, RegistrationProvider } from './interfaces';
import { IRegistryData, RegistryData } from './registry-data';
import { IContainer } from './container.interface';
import { ClassNotInjectableError, InvalidProviderProvidedError, NoProviderError } from './exceptions';
import { FactoryFunction, IFactory, IRegistryData, RegistryData } from './registry-data';
import { IContainer, IContainerOptions } from './container.interface';
import { InvalidProviderProvidedError, NoProviderError } from './exceptions';
import { INJECTABLE_MD_KEY, INJECTIONS_MD_KEY } from './metadata/keys';
import { IMetadataAnnotator } from './metadata/metadata-annotator.interface';
import { AnnotatorProvider } from './metadata/index';

const MetadataAnnotator: IMetadataAnnotator = AnnotatorProvider.get();

export class Container implements IContainer {
private static DEFAULT_LIFE_TIME = LifeTime.Persistent;
private registry: Map<ProviderToken, IRegistryData> = new Map();
private parent: IContainer;
private defaultLifeTime: LifeTime = Container.DEFAULT_LIFE_TIME;

constructor(private parent?: IContainer) {}
constructor(options?: IContainerOptions) {
if (options) {
this.parent = <IContainer> options.parent;
this.defaultLifeTime = options.defaultLifeTime || this.defaultLifeTime;
}
}

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

public createScope(): IContainer {
return new Container(this);
return new Container({ parent: this });
}

public setParent(parent: IContainer): void {
this.parent = parent;
public createChild(): IContainer {
return this.createScope();
}

public createChild(): IContainer {
return new Container(this);
public setParent(parent: IContainer): void {
this.parent = parent;
}

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

if (registryData.factory) {
let injections: ProviderToken[] = [];

if (registryData.injections) {
injections = registryData.injections.map(i => this.resolveInternal(i, traceMessage));
}

return registryData.factory(...injections);
}

const constructor: IConstructor = registryData.cls;

const isInjectable: boolean = this.isInjectable(constructor);
if (!isInjectable) {
throw new ClassNotInjectableError(constructor.name);
}

const instance: IInjectionInstance = this.createInstance(constructor, traceMessage);
const instance: IInjectionInstance = this.instantiateWithFactory(registryData.factory, traceMessage);

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

if (provider.useValue) {
registryData.instance = provider.useValue;
} else if (provider.useClass) {
registryData.cls = provider.useClass;
} else if (provider.useFactory) {
registryData.factory = provider.useFactory;
registryData.injections = <ProviderToken[]> provider.inject;
}
} else {
const factoryValue = provider.useFactory || provider.useClass;
const isClass: boolean = this.isInjectable(factoryValue);

registryData.factory = {
value: factoryValue,
isClass
};

if (isClass) {
registryData.factory.inject = this.retrieveInjectionsFromClass(<IConstructor> registryData.factory.value);
} else {
registryData.factory.inject = this.convertTokensToInjectionMd(<ProviderToken> provider.inject);
}

registryData.lifeTime = provider.lifeTime || LifeTime.Persistent;
registryData.lifeTime = provider.lifeTime || this.defaultLifeTime;
}

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

private createInstance(cls: IConstructor, message: string): IInjectionInstance {
const injectionsMd: IInjectionMd[] = this.getInjections(cls);
const resolvedInjections: any[] = injectionsMd.map(injectionMd => this.resolveInternal(injectionMd.token, message));
private convertTokensToInjectionMd(tokens: ProviderToken[]): IInjectionMd[] {
let injections: IInjectionMd[] = [];

if (tokens) {
injections = tokens.map((token: ProviderToken, index: number) => {
return {
token,
parameterIndex: index
};
});
}

return injections;
}

private instantiateWithFactory(factory: IFactory, traceMessage: string): IInjectionInstance {
const injections = <IInjectionMd[]> factory.inject;

const resolvedInjections: any[] = injections.map(injection => this.resolveInternal(injection.token, traceMessage));

const args: any[] = [];
injectionsMd.forEach((injection: IInjectionMd, index) => {
injections.forEach((injection: IInjectionMd, index: number) => {
args[injection.parameterIndex] = resolvedInjections[index];
});

return new cls(...args);
if (factory.isClass) {
return new (<IConstructor> factory.value)(...args);
} else {
return (<FactoryFunction> factory.value)(...args);
}
}

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

private getInjections(cls: any): IInjectionMd[] {
return MetadataAnnotator.getMetadata(INJECTIONS_MD_KEY, cls) || [];
private retrieveInjectionsFromClass(cls: IConstructor): IInjectionMd[] {
const injections: IInjectionMd[] = MetadataAnnotator.getMetadata(INJECTIONS_MD_KEY, cls) || [];
return this.convertTokensToInjectionMd(injections);
}
}
16 changes: 2 additions & 14 deletions src/lib/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,7 @@ export function Injectable(injections?: ProviderToken[]) {

if (injections && Array.isArray(injections)) {
const injectionMd: IInjectionMd[] = MetadataAnnotator.getMetadata(INJECTIONS_MD_KEY, target) || [];

injections.forEach((injectionToken, injectionIndex) => {
injectionMd.push({
token: injectionToken,
parameterIndex: injectionIndex
});
});

injections.forEach(token => injectionMd.push(token));
MetadataAnnotator.defineMetadata(INJECTIONS_MD_KEY, injectionMd, target);
}
};
Expand All @@ -27,12 +20,7 @@ export function Injectable(injections?: ProviderToken[]) {
export function Inject(token: any) {
return (target: object, propertyKey: string | symbol, parameterIndex: number) => {
const injections: IInjectionMd[] = MetadataAnnotator.getMetadata(INJECTIONS_MD_KEY, target) || [];

injections.push({
token,
parameterIndex
});

injections.push(token);
MetadataAnnotator.defineMetadata(INJECTIONS_MD_KEY, injections, target);
};
}
18 changes: 11 additions & 7 deletions src/lib/registry-data.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { IConstructor, IInjectionInstance, LifeTime, ProviderToken } from './interfaces';
import { IConstructor, IInjectionInstance, IInjectionMd, LifeTime } from './interfaces';

export type FactoryFunction = (...args: any[]) => any;

export interface IFactory {
value: IConstructor | FactoryFunction;
isClass: boolean;
inject?: IInjectionMd[];
}

export interface IRegistryData {
instance: IInjectionInstance;
cls: IConstructor;
factory: (...args: any[]) => any;
injections: ProviderToken[];
factory: IFactory;
lifeTime: LifeTime;
}

export class RegistryData {
public instance: IInjectionInstance;
public cls: IConstructor;
public factory: (...args: any[]) => any;
public injections: ProviderToken[];
public factory: IFactory;
public lifeTime: LifeTime;
}
Loading