InversifyJS icon indicating copy to clipboard operation
InversifyJS copied to clipboard

Entities with private container and lazyInject

Open CrazyFlasher opened this issue 2 years ago • 1 comments

  1. For example I have class AppFactoy with private Container
  2. I have multiple instances of AppFactoy that have their own bindings.

Simple raw sample with ordinary injections (not lazy):

it('testInject', () =>
{
    class AppFactory
    {
        private container: Container = new Container({autoBindInjectable: true});

        public mapToValue(type:any, to:any):void
        {
            if (this.container.isBound(type))
            {
                this.container.unbind(type);
            }

            this.container.bind(type).toConstantValue(to);
        }

        public getInstance(type:any):any
        {
            return this.container.get(type);
        }
    }

    @injectable()
    class Obj
    {
        @inject("string")
        private _s:string;

        get s(): string
        {
            return this._s;
        }
    }

    class Main
    {
        private f_1:AppFactory = new AppFactory();
        private f_2:AppFactory = new AppFactory();

        constructor()
        {
            this.f_1.mapToValue("string", "aaa");
            this.f_2.mapToValue("string", "bbb");

            let o_1:Obj = this.f_1.getInstance(Obj);
            let o_2:Obj = this.f_2.getInstance(Obj);

            expect(o_1.s).equals("aaa");
            expect(o_2.s).equals("bbb");

            this.f_1.mapToValue(Obj, o_1);
            this.f_2.mapToValue(Obj, o_2);

            this.f_1.mapToValue("string", "ccc");
            this.f_2.mapToValue("string", "ddd");

            let o_3:Obj = this.f_1.getInstance(Obj);
            let o_4:Obj = this.f_2.getInstance(Obj);

            // which is correct, because values are not re-injected
            expect(o_3.s).equals("aaa");
            expect(o_4.s).equals("bbb");
        }
    }

    new Main();
});

The question is how can I achieve this using lazy injection of private _s:string having separate lazyInject constants with each AppFactory container?

CrazyFlasher avatar Oct 08 '21 10:10 CrazyFlasher

This is a workaround:

  1. We create globalContainer in static scope (or global scope? I don't know how to name it correctly).
  2. Before each lazy injection we merge bindings from AppFactory container into globalContainer and after we clear it by calling unbindAll. Though it works as I expect, may be there is a more elegant way to implement that?
it('testLazyInject', () =>
{
    function copyDictionary(
        origin: interfaces.Lookup<interfaces.Binding<any>>,
        destination: interfaces.Lookup<interfaces.Binding<any>>
    ) {

        origin.traverse((key, value) => {
            value.forEach((binding) => {
                destination.add(binding.serviceIdentifier, binding.clone());
            });
        });

    }
    
    const globalContainer: Container = new Container();

    const {lazyInject} = getDecorators(globalContainer, false);

    class AppFactory
    {
        public container: Container = new Container({autoBindInjectable: true});

        public mapToValue(type:any, to:any, name?:string):void
        {
            if (!name)
            {
                if (this.container.isBound(type))
                {
                    this.container.unbind(type);
                }

                this.container.bind(type).toConstantValue(to);
            } else
            {
                if (this.container.isBoundNamed(type, name))
                {
                    this.container.unbind(type);
                }

                this.container.bind(type).toConstantValue(to).whenTargetNamed(name);
            }
        }

        public getInstance(type:any, name?:any):any
        {
            return !name ? this.container.get(type) : this.container.getNamed(type, name);
        }
    }

    @injectable()
    class Obj
    {
        @lazyInject("string")
        private _s:string;

        get s(): string
        {
            return this._s;
        }
    }

    class Main
    {
        private f_1:AppFactory = new AppFactory();
        private f_2:AppFactory = new AppFactory();

        constructor()
        {
            this.f_1.mapToValue("string", "aaa");
            this.f_2.mapToValue("string", "bbb");

            copyDictionary(getBindingDictionary(this.f_1.container), getBindingDictionary(globalContainer));

            let o_1:Obj = new Obj();
            expect(o_1.s).equals("aaa");

            globalContainer.unbindAll();

            copyDictionary(getBindingDictionary(this.f_2.container), getBindingDictionary(globalContainer));

            let o_2:Obj = new Obj();
            expect(o_2.s).equals("bbb");

            globalContainer.unbindAll();

            this.f_1.mapToValue(Obj, o_1, "o1");
            this.f_2.mapToValue(Obj, o_2, "o2");

            this.f_1.mapToValue("string", "ccc");
            this.f_2.mapToValue("string", "ddd");

            copyDictionary(getBindingDictionary(this.f_1.container), getBindingDictionary(globalContainer));

            let o_3:Obj = this.f_1.getInstance(Obj, "o1");

            // string is re-injected to already existing objects
            expect(o_3.s).equals("ccc");

            globalContainer.unbindAll();

            copyDictionary(getBindingDictionary(this.f_2.container), getBindingDictionary(globalContainer));

            let o_4:Obj = this.f_2.getInstance(Obj, "o2");

            // string is re-injected to already existing objects
            expect(o_4.s).equals("ddd");

            expect(o_3).equals(o_1);
            expect(o_4).equals(o_2);
        }
    }

    new Main();
});

CrazyFlasher avatar Oct 08 '21 11:10 CrazyFlasher