Skip to content

allow parametrized resolution of services through getParametrized method #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
18 changes: 16 additions & 2 deletions src/container/container-binding-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,23 @@ export class IoCBindConfig implements Config {
this.targetSource = targetSource;
if (this.source === targetSource) {
this.factory((context) => {
const params = this.getParameters(context);
const requestParameters = context && context.getRequestSpecificParameters();
if(requestParameters) {
// reset request specific parameters before resolving other parameters
// in order to avoid propagation of request-specific params to dependencies
context.resetRequestSpecificParameters();
}

const params = this.getParameters(context) || [];

if(requestParameters) {
for(let i = 0; i < requestParameters.length; i++) {
params[i] = requestParameters[i];
}
}

const constructor = this.decoratedConstructor || target;
return (params ? new constructor(...params) : new constructor());
return (params.length ? new constructor(...params) : new constructor());
});
} else {
this.factory((context) => {
Expand Down
4 changes: 2 additions & 2 deletions src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ function InjectParamDecorator(target: Function, propertyKey: string | symbol, pa
const config = IoCContainer.bind(target);
config.paramTypes = config.paramTypes || [];
const paramTypes: Array<any> = Reflect.getMetadata('design:paramtypes', target);
config.paramTypes.unshift(paramTypes[parameterIndex]);
config.paramTypes[parameterIndex] = paramTypes[parameterIndex];
}
}

Expand All @@ -233,6 +233,6 @@ function InjectValueParamDecorator(target: Function, propertyKey: string | symbo
if (!propertyKey) { // only intercept constructor parameters
const config = IoCContainer.bind(target);
config.paramTypes = config.paramTypes || [];
config.paramTypes.unshift(value);
config.paramTypes[_parameterIndex] = value;
}
}
4 changes: 4 additions & 0 deletions src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export type ObjectFactory = (context?: BuildContext) => Object;
* The context of the current Container resolution.
*/
export abstract class BuildContext {
public resetRequestSpecificParameters() {
// do nothing. default implementation does not perform any action
}
public getRequestSpecificParameters() { return []; }
public abstract resolve<T>(source: Function & { prototype: T }): T;
public abstract build<T>(source: Function & { prototype: T }, factory: ObjectFactory): T;
}
Expand Down
28 changes: 28 additions & 0 deletions src/typescript-ioc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ export class Container {
return IoCContainer.get(source, new ContainerBuildContext());
}

/**
* Retrieve an object from the container. It will resolve all dependencies and apply any type replacement
* before return the object.
* If there is no declared dependency to the given source type, an implicity bind is performed to this type.
* if any number of items is provided in the 'params' argument - these items will be used as constructor arguments
* taking over the potential bindings
* @param source The dependency type to resolve
* @param contextParams list of X parameters, which will be passed as first X constructor arguments
* @return an object resolved for the given source type;
*/
public static getParametrized<T>(source: Function & { prototype: T }, ...contextParams: Array<any>): T {
return IoCContainer.get(source, new ParametrizedBuildContext(contextParams));
}

/**
* Retrieve a type associated with the type provided from the container
* @param source The dependency type to resolve
Expand Down Expand Up @@ -178,4 +192,18 @@ class ContainerBuildContext extends BuildContext {
public resolve<T>(source: Function & { prototype: T }): T {
return IoCContainer.get(source, this);
}
}

export class ParametrizedBuildContext extends ContainerBuildContext {
constructor(private contextParams: Array<any>) {
super();
}

public getRequestSpecificParameters() {
return this.contextParams;
}

public resetRequestSpecificParameters() {
this.contextParams = [];
}
}
108 changes: 108 additions & 0 deletions test/integration/ioc-container-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,4 +671,112 @@ describe('The IoC Container Config.withParams()', () => {
const instance: WithParamClass = Container.get(WithParamClass);
expect(instance.date).toBeDefined();
});
});

describe('shoould handle parametrized requests', () => {
let constructorsMultipleArgs: Array<any> = new Array<any>();

beforeEach(() => {
constructorsMultipleArgs = [];
});

class SingleDynamicArgConstructor {
public injectedString: string;
constructor(stringArg: string) {
this.injectedString = stringArg;
}
}

it('should let single argument be defined in runtime', () => {
const instance = Container.getParametrized(SingleDynamicArgConstructor, 'str1');
expect(instance.injectedString).toEqual('str1');
});

class SingleInjectedArgConstructor {
public injectedDate: Date;
constructor(@Inject date: Date) {
this.injectedDate = date;
}
}

it('should let single argument be defined in runtime and override injected value if any', () => {
const customDate = new Date();
const instance = Container.getParametrized(SingleInjectedArgConstructor, customDate);
expect(instance.injectedDate).toEqual(customDate);
});

class Aaaa { }
class Bbbb { }
class Cccc { }

class Dddd {
constructor(@Inject a: Aaaa, @Inject b: Bbbb, @Inject c: Cccc) {
constructorsMultipleArgs.push(a);
constructorsMultipleArgs.push(b);
constructorsMultipleArgs.push(c);
}
}

it('should let single resolution parameter be passed as a first argument. Must override injected value while resolving multiple', () => {
const aaaaInstance = new Aaaa();

const instance = Container.getParametrized(Dddd, aaaaInstance);
expect(instance).toBeDefined();
expect(constructorsMultipleArgs[0]).toBeDefined();
expect(constructorsMultipleArgs[1]).toBeDefined();
expect(constructorsMultipleArgs[2]).toBeDefined();
expect(constructorsMultipleArgs[0]).toEqual(aaaaInstance);
expect(constructorsMultipleArgs[1]).toBeInstanceOf(Bbbb);
expect(constructorsMultipleArgs[2]).toBeInstanceOf(Cccc);
});


it('should let multiple resolution parameters be passed as a first arguments. Must override injected values while resolving multiple', () => {
const aaaaInstance = new Aaaa();
const bbbbInstance = new Bbbb();

const instance = Container.getParametrized(Dddd, aaaaInstance, bbbbInstance);
expect(instance).toBeDefined();
expect(constructorsMultipleArgs[0]).toEqual(aaaaInstance);
expect(constructorsMultipleArgs[1]).toEqual(bbbbInstance);
expect(constructorsMultipleArgs[2]).toBeInstanceOf(Cccc);
});

class DdddWithExpectedResolutionParam {
constructor(a: Aaaa, @Inject b: Bbbb, @Inject c: Cccc) {
constructorsMultipleArgs.push(a);
constructorsMultipleArgs.push(b);
constructorsMultipleArgs.push(c);
}
}

it('should let single resolution parameter be passed as a first argument if not injectable', () => {
const aaaaInstance = new Aaaa();

const instance = Container.getParametrized(DdddWithExpectedResolutionParam, aaaaInstance);
expect(instance).toBeDefined();
expect(constructorsMultipleArgs[0]).toEqual(aaaaInstance);
expect(constructorsMultipleArgs[1]).toBeInstanceOf(Bbbb);
expect(constructorsMultipleArgs[2]).toBeInstanceOf(Cccc);
});

class ClassWhichIsDependency {
constructor(public paramDep: string){
}
}

class ClassWhichHasDependency {
constructor(public paramDep: string, @Inject public classDep: ClassWhichIsDependency){
}
}

it('must not pass dynamic resolution parameters to any instances other than the root one', () => {
const dynamicResolutionParameter = 'theOne';

const instance = Container.getParametrized(ClassWhichHasDependency, dynamicResolutionParameter);
expect(instance).toBeDefined();
expect(instance.paramDep).toEqual('theOne');
expect(instance.classDep).toBeInstanceOf(ClassWhichIsDependency);
expect(instance.classDep.paramDep).toEqual(undefined);
});
});
28 changes: 28 additions & 0 deletions test/unit/decorators.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ describe('@Inject decorator', () => {

expect(testFunction).toThrow(new TypeError('Invalid @Inject Decorator declaration.'));
});

it('should inject into proper arguments despite previous arguments are not decorated', () => {
const config: any = {};
mockBind.mockReturnValue(config);

class ConstructorInjected {
constructor(public anotherDate: Date,
@Inject public myProp: String) {
}
}
expect(mockBind).toBeCalledWith(ConstructorInjected);
expect(config.paramTypes[0]).toEqual(undefined);
expect(config.paramTypes[1]).toEqual(String);
});
});

const mockInjectValueProperty = IoCContainer.injectValueProperty as jest.Mock;
Expand Down Expand Up @@ -107,6 +121,20 @@ describe('@InjectValue decorator', () => {

expect(testFunction).toThrow(new TypeError('Invalid @InjectValue Decorator declaration.'));
});

it('should inject into proper arguments despite previous arguments are not decorated', () => {
const config: any = {};
mockBind.mockReturnValue(config);

class ConstructorInjected {
constructor(public anotherDate: Date,
@InjectValue('myString') public myProp: String) {
}
}
expect(mockBind).toBeCalledWith(ConstructorInjected);
expect(config.paramTypes[0]).toEqual(undefined);
expect(config.paramTypes[1]).toEqual('myString');
});
});

const mockTo = jest.fn();
Expand Down