3

How partial properties injection can be done with Inversify?

Let's say we have class

class MyClass {
    constructor(
        @inject(EXTERNAL_A) private externalA: ExternalClassA,
        private b: string
    ) {}
}

How can I use this MyClass with inversify in other classes in case if all possible values of b are known on compile time. So, I need just to instances of MyClass one with b = "a" and another one with b = "b".

The only solution that I found at the moment is two define two different bindings for that or using factory to directly call new MyClass.

In the first case I need to write something like that only

container.bind<MyClass>(CLASS_A).toConstantValue(
    new MyClass(container.get<ExternalClassA>(EXTERNAL_A), "a")
);

container.bind<MyClass>(CLASS_B).toConstantValue(
    new MyClass(container.get<ExternalClassA>(EXTERNAL_A), "b")
);

Looks pretty messy and not solving next issue, as well as factories not solving it. If I have deep objects hierarchy in this case, I need to construct them all through this chain of manual objects construction.

What is the best way here?

Task with a star, if it possible to resolve some dependencies tree with replacement of the same single dependency with provided one? Like, we can have something like

 |-----B-----e
A|-----C-----e
 |-----D
 |-----e

So I wan't to replace e dependency with my constructed one. How can I achieve this?

Artsiom Miksiuk
  • 3,896
  • 9
  • 33
  • 49

2 Answers2

1

You can bind a factory to your inversify container using toFactory. Note that your factory can take arguments.

See https://github.com/inversify/InversifyJS/blob/master/wiki/factory_injection.md

Here's an example for your use case specifically.

container
    .bind<(b: string) => MyClass>("MyClassFactory")
    .toFactory<MyClass>((context: interfaces.Context) => {
      return (b: string) => {
        const classA = context.container.get<ExternalClassA>("EXTERNAL_A")

        return new MyClass(classA, b)
      }
    })

So we're binding to identifier "MyClassFactory" a function that returns an instance of MyClass depending on what args you pass it.

Since we get EXTERNAL_A from the container via inversify, we aren't instantiating it ourselves, so we don't need to worry about what dependencies it has itself.

To use your factory...

@injectable()
class SomeClass {
  @inject("MyClassFactory") private myClassFactory!: (b: string) => MyClass

  someMethod() {
    const myClassA = this.myClassFactory('a')
    const myClassB = this.myClassFactory('b')
  }
} 

One thing to note is that, in the question, you're using toConstantValue and so your classes are being constructed and bound as singletons. I don't know if that's intentional, but if so, you could still use the above factory to do that...

container.bind<MyClass>("CLASS_A").toDynamicValue((context: interfaces.Context) => { 
  const myClassFactory = context.container.get<(b: string) => MyClass>("MyClassFactory")
  return myClassFactory('a')
})

container.bind<MyClass>("CLASS_B").toDynamicValue((context: interfaces.Context) => { 
  const myClassFactory = context.container.get<(b: string) => MyClass>("MyClassFactory")
  return myClassFactory('b')
})

You could create the singletons programmatically too. If you had some array of {identifier: "CLASS_A", factoryArg: 'a'} type objects, you could loop over it and create dynamic values as above.

Regarding your last Q, my answer is probably too long now, but check out this section in the docs, it may help! https://github.com/inversify/InversifyJS/blob/master/wiki/recipes.md#overriding-bindings-on-unit-tests

Matt Wills
  • 676
  • 6
  • 11
0

Hi,

I'm afraid there is no perfect solution. On this cases I normally use a factory pattern with a set or setup method.

Example:

interface MyInterface {
     myPublicMethod();
}

type MyInterfaceFactoryA = () => MyInterface;
type MyInterfaceFactoryB = () => MyInterface;

class MyClass extends MyInterface {

     private b: string;

     constructor(
          @inject(EXTERNAL_A) private externalA: ExternalClassA,
     ) {}

     public setup(b: string): void {
          this.b = b;
     }

}

When you are setting up your container, normally within a ContainerModule, you need to do something like this:

 bind<MyClass>("MyClass").to(MyClass);

 container.bind<MyInterface>(CLASS_A).toConstantValue(
      const myClass = context.container.get<MyClass>("MyClass");
      return myClass.setup("a");
 );

 container.bind<MyInterface>(CLASS_B).toConstantValue(
     const myClass = context.container.get<MyClass>("MyClass");
     return myClass.setup("a");
 );

And then just use it:

 class MyOtherClass {

      constructor(
          @inject(CLASS_A) private _myInterfaceInstanceA: MyInterface,
          @inject(CLASS_B) private _myInterfaceInstanceB: MyInterface) {

      }

 }

Hope it helps you. You can find more information about this on: https://github.com/inversify/InversifyJS/issues/530

jpsfs
  • 708
  • 2
  • 7
  • 24