Search code examples
node.jsinversion-of-controlinversifyjs

InversifyJS : Error when retrieving object from container


I am using InversifyJS to setup some kind-of dynamic binding and running it on NodeJS. The problem I have is that I get errors, but without messages and only a stacktrace.

The Main class

export class Main {
    public static getCalculator(config: string): Calculator {
        try {
            var container: Container = new KeyFeatureContainer(config).getContainer();
            debugger;
            // error here !
            return container.get<Calculator>(TYPES.Calculator);
        } catch (error) {
            debugger;
            return null;
        }
    }
}

This class calls the KeyFeatureContainer with a json string, used to configure all the bindings (in a sort-of dynamic way), and then retrieves the container.

The KeyFeatureContainer class

export class KeyFeatureContainer {
    private _container: Container;

    public constructor(config: string) {
        var jsonConfig: any[] = JSON.parse(config);

        this._container = new Container();
        this._container.bind<Calculator>(TYPES.Calculator).to(KeyFeatureCalculator);

        for (var i = 0; i < jsonConfig.length; i++) {
            if (jsonConfig[i].active) {
                this.parseConfigKeyFeatures(jsonConfig[i].id);
                this.parseConfigParams(jsonConfig[i].params);
            }
        }
    }

    public getContainer(): Container {
        debugger;
        return this._container;
    }

    private parseConfigKeyFeatures(id: string): void {
        var keyFeatureContainerModule: ContainerModule = rootContainer.get<KeyFeatureContainerModule>(id).getContainerModule();

        if (keyFeatureContainerModule != null)
            this._container.load(keyFeatureContainerModule);
    }

    private parseConfigParams(params: Array<{ name: string, value: any }>): void {
        for (var param of params)
            this._container.bind(param.name).to(param.value);
    }
}

This class receives the json configuration and loads, following an ID, a ContainerModule which contains the binding between the interface and the concrete implementation. The rootContainer specifies the ContainerModule to retrieve following an ID.

The rootContainer

var rootContainer: Container = new Container();

rootContainer.bind<KeyFeatureContainerModule>(KEYFEATURES.DrugHoliday).to(DrugHolidayContainerModule);
rootContainer.bind<KeyFeatureContainerModule>(KEYFEATURES.MissingDay).to(MissingDayContainerModule);

export default rootContainer;

And the related ContainerModules (the second one is identical, just the PARAMS that are different for each)

@injectable()
export class MissingDayKeyFeature implements KeyFeature {
    @inject(PARAMS.MissingDayParams.NbIntakesLimit)
    private _nbIntakesLimit: number;

    @inject(PARAMS.MissingDayParams.ExtraParamA)
    private _extraParamA: any;

    @inject(PARAMS.MissingDayParams.ExtraParamB)
    private _extraParamB: any;

    public init(): void {
        console.log("init() at MissingDay");

        console.log("nbIntakesLimit = " + this._nbIntakesLimit);
        console.log("extraParamA = " + this._extraParamA);
        console.log("extraParamB = " + this._extraParamB);
    }

    public calculate(): void {
        console.log("calculate() at MissingDay");
    }

    public finish(): void {
        console.log("finish() at MissingDay");
    }
}

@injectable()
export class MissingDayContainerModule implements KeyFeatureContainerModule {
    public getContainerModule(): ContainerModule {
        return new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind) => {
            bind<KeyFeature>(TYPES.KeyFeature).to(MissingDayKeyFeature);
        });
    }
}

And finally, the ServiceIdentifiers used to setup the whole (separated files)

let TYPES = {
    KeyFeature: "KeyFeature",
    Calculator: "Calculator"
}

export default TYPES;

let PARAMS = {
    DrugHolidayParams: {
        NbDaysLimit: "nbDaysLimit",
        ExtraParamA: "extraParamDHA",
        ExtraParamB: "extraParamDHB"
    },
    MissingDayParams: {
        NbIntakesLimit: "nbIntakesLimit",
        ExtraParamA: "extraParamMDA",
        ExtraParamB: "extraParamMDB"
    }
}

export default PARAMS;

let KEYFEATURES = {
    MissingDay: "MissingDayKeyFeature",
    DrugHoliday: "DrugHolidayKeyFeature"
}

export default KEYFEATURES;

Finally the JSON input (formatted for convenience)

[{
        "id": "DrugHolidayKeyFeature",
        "active": true,
        "params": [{
                "name": "nbDaysLimit",
                "value": 3
            }, {
                "name": "extraParamDHA",
                "value": "DHA"
            }, {
                "name": "extraParamDHB",
                "value": "DHB"
            }
        ]
    }, {
        "id": "MissingDayKeyFeature",
        "active": false,
        "params": [{
                "name": "nbIntakesLimit",
                "value": 0
            }, {
                "name": "extraParamMDA",
                "value": "MDA"
            }, {
                "name": "extraParamMDB",
                "value": "MDB"
            }
        ]
    }
]

I use a simple Test script just to output the values and see if all bindings are done correctly

import { Main } from "./Main";
var json: string = *json above*;
Main.getCalculator(json).calculate();

But I end up with this error

Error
<     at _createSubRequests (D:\Projects\226RD\nodejs\phantomjs_2016_by_DM\final\node_modules\inversify\lib\planning\planner.js:106:19)
<     at Object.plan (D:\Projects\226RD\nodejs\phantomjs_2016_by_DM\final\node_modules\inversify\lib\planning\planner.js:125:5)
<     at D:\Projects\226RD\nodejs\phantomjs_2016_by_DM\final\node_modules\inversify\lib\container\container.js:205:37
<     at Container._get (D:\Projects\226RD\nodejs\phantomjs_2016_by_DM\final\node_modules\inversify\lib\container\container.js:198:44)
<     at Container.get (D:\Projects\226RD\nodejs\phantomjs_2016_by_DM\final\node_modules\inversify\lib\container\container.js:163:21)
<     at Function.getCalculator (D:\Projects\226RD\nodejs\phantomjs_2016_by_DM\final\transpiled\KFCalc\Main.js:9:30)
<     at Object.<anonymous> (D:\Projects\226RD\nodejs\phantomjs_2016_by_DM\final\transpiled\KFCalc\Test.js:4:13)
<     at Module._compile (module.js:573:32)
<     at Object.Module._extensions..js (module.js:582:10)
<     at Module.load (module.js:490:32)

There is no clue about what is happening, and I can't figure out what I have done wrong. I can't get<Calculator> nor get<KeyFeature>, but if I do :

container.isBound(TYPES.Calculator) // returns true !!!

and same applies for all bound items.

Thank you for any pointers, I'm out of ideas.

+-- @types/[email protected]
+-- [email protected]
+-- [email protected]
+-- [email protected]

EDIT : Forgot the KeyFeatureCalculator

@injectable()
export class KeyFeatureCalculator implements Calculator {
    // multi-injection of bound key features
    private _keyFeatureCalculators: KeyFeature[] = [];

    public constructor(@multiInject(TYPES.KeyFeature) keyFeatureCalculators: KeyFeature[]) {
        this._keyFeatureCalculators = keyFeatureCalculators;
    }

    public calculate(): void {
        console.log("calculate() at KeyFeatureCalculator");

        for (var calculator of this._keyFeatureCalculators) {
            calculator.init();
            calculator.calculate();
            calculator.finish();
        }
    }
}

Also, all my files (or almost) import inject, injectable from inversify and also import reflect-metadata


Solution

  • Error resolved : For those who didn't pay attention, each KeyFeature object has its properties injected, and their types are number or any. Since the values are retrieved from my JSON, they are typed as any and thus the compiler doesn't warn me that the binding bind(xxx).to(yyy) requires to be a newable element !

    So, in my example, the yyy was something like 3, which is a number and thus not boundable. I changed to bind(xxx).toConstantValue(yyy) and everything works !