Skip to content

Expose default container instances to external containers #264

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

Closed
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
2 changes: 1 addition & 1 deletion gulpfile.ts
Original file line number Diff line number Diff line change
@@ -204,7 +204,7 @@ export class Gulpfile {
*/
@SequenceTask()
tests() {
return ["compile", "tslint", "coveragePost", "coverageRemap"];
return ["tslint", "compile", "coveragePost", "coverageRemap"];
}

}
39 changes: 34 additions & 5 deletions src/container.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@

type DefaultContainer = {
get<T>(someClass: { new(...args: any[]): T } | Function): T;
getInstances(): Entry[];
isEmpty(): boolean;
};

export type Entry = { type: Function, object: any };

/**
* Container options.
*/
@@ -14,15 +22,20 @@ export interface UseContainerOptions {
*/
fallbackOnErrors?: boolean;

/**
* If the default container has registered instances this function allow to class-validator to expose the current state.
* @param instances
*/
registerExistingInstances?: (instances: Entry[]) => void;
}

/**
* Container to be used by this library for inversion control. If container was not implicitly set then by default
* container simply creates a new instance of the given class.
*/
const defaultContainer: { get<T>(someClass: { new (...args: any[]): T }|Function): T } = new (class {
private instances: { type: Function, object: any }[] = [];
get<T>(someClass: { new (...args: any[]): T }): T {
const defaultContainer: DefaultContainer = new (class {
private instances: Entry[] = [];
get<T>(someClass: { new(...args: any[]): T }): T {
let instance = this.instances.find(instance => instance.type === someClass);
if (!instance) {
instance = { type: someClass, object: new someClass() };
@@ -31,9 +44,17 @@ const defaultContainer: { get<T>(someClass: { new (...args: any[]): T }|Function

return instance.object;
}

getInstances(): Entry[] {
return this.instances;
}

isEmpty(): boolean {
return this.instances.length === 0;
}
})();

let userContainer: { get<T>(someClass: { new (...args: any[]): T }|Function): T };
let userContainer: { get<T>(someClass: { new(...args: any[]): T } | Function): T };
let userContainerOptions: UseContainerOptions;

/**
@@ -42,12 +63,20 @@ let userContainerOptions: UseContainerOptions;
export function useContainer(iocContainer: { get(someClass: any): any }, options?: UseContainerOptions) {
userContainer = iocContainer;
userContainerOptions = options;

if (userContainer && !defaultContainer.isEmpty()) {
if (options && typeof (options.registerExistingInstances) === "function") {
options.registerExistingInstances(defaultContainer.getInstances());
} else {
throw new Error("class-validator is unable to register existing components on userContainer. Please provide a 'registerExistingInstances' function in options.");
}
}
}

/**
* Gets the IOC container used by this library.
*/
export function getFromContainer<T>(someClass: { new (...args: any[]): T }|Function): T {
export function getFromContainer<T>(someClass: { new(...args: any[]): T } | Function): T {
if (userContainer) {
try {
const instance = userContainer.get(someClass);
74 changes: 74 additions & 0 deletions test/functional/container.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import "es6-shim";
import { IsNotEmpty } from "../../src/decorator/decorators";
import { Validator } from "../../src/validation/Validator";
import { should, use } from "chai";

import * as chaiAsPromised from "chai-as-promised";
import { useContainer } from "../../src";

should();
use(chaiAsPromised);

// -------------------------------------------------------------------------
// Setup
// -------------------------------------------------------------------------

const validator = new Validator();

// -------------------------------------------------------------------------
// Specifications: common decorators
// -------------------------------------------------------------------------

describe("container", function () {
afterEach(function () {
useContainer(undefined);
});

it("should throw an error if defaultContainer is not empty and useContainer hasn't registerExistingInstances function in options", function () {
class MyClass {
@IsNotEmpty()
title: string;
}

const model = new MyClass();

try {
useContainer({ get: () => { null; } });
} catch (err) {
err.message.should.be.equal("class-validator is unable to register existing components on userContainer. Please provide a \'registerExistingInstances\' function in options.");
}
});

it("should invoke registerExistingInstances function with instances", function () {
const instances: { type: Function, object: any }[] = [];

const container = {
get<T>(someClass: { new(...args: any[]): T }): T {
let instance = instances.find(instance => instance.type === someClass);
if (!instance) {
instance = { type: someClass, object: new someClass() };
instances.push(instance);
}

return instance.object;
}
};

const options = {
registerExistingInstances: (newInstances: any[]) => {
newInstances.forEach(instance => instances.push(instance));
}
};

useContainer(container, options);

class MyClass {
@IsNotEmpty()
title: string;
}

const model = new MyClass();

instances.length.should.not.be.equal(0);
});
});