Tim S
Tim S

Reputation: 707

Factory with parameters in InversifyJS

I'm using InversifyJS with TypeScript.

Let's say I have a class which has a mixture of injected and non-injected constructor parameters:

@injectable()
export class MyClass {
    constructor(
      foo: Foo, // This thing isn't injectable
      @inject(Bar) bar: Bar // This thing is
   ){
      ...
   }
}

I would like to inject a factory for this class into some other class and then invoke it with a value for the first parameter.

@injectable()
export class SomeOtherClass {
   constructor(
      @inject("Factory<MyClass>") myClassFactory: (foo: Foo) => MyClass
   ){
      const someFoo = new Foo();
      const myClass = myClassFactory(someFoo);
   }
}

My question: is there some auto-magic in Inversify that will allow me to inject this factory?

The best I've come up with so far is this:

bind<interfaces.Factory<MyClass>>("Factory<MyClass>").toFactory(context =>
    (foo: Foo) => 
        new MyClass(foo, context.kernel.get<Bar>(Bar))
);

But that means I'm explicitly new()-ing MyClass and every new injectable dependency of MyClass has to be added here too.

Is there a better way? Perhaps something based on parameter names like Ninject Factory Extensions?

Upvotes: 3

Views: 4647

Answers (1)

Remo H. Jansen
Remo H. Jansen

Reputation: 24941

I'm the author of InversifyJS. I don't think there is a problem about using new inside the factory, remember that the job of the factory is to create a new instance.

It is true that calling container.get<T> multiple times inside a factory is not a good thing.

I can suggest breaking the initialization of MyClass into to phases:

The class instantiated by the factory:

@injectable()
export class MyClass {

    public c: C; // This thing isn't injectable
    public d: D; // This thing isn't injectable

    // PHASE 1: instantiation
    constructor(
      @inject(A) a: A // This thing is
      @inject(B) b: B // This thing is
   ){
      ...
   }

   // PHASE 2: initialization
   public init(c: C, d: D) {
      this.c = c;
      this.d = d;
   }

}

The class that uses the factory:

@injectable()
export class SomeOtherClass {
   constructor(
      @inject("Factory<MyClass>") myClassFactory: (c: C, d: D) => MyClass
   ){
      // you only use "new" with entities that are not injected by inversify
      const c = new C(); 
      const d = new D();
      const myClass = myClassFactory(c, d);
   }
}

And the factory:

bind<interfaces.Factory<MyClass>>("Factory<MyClass>").toFactory(context =>
    (c: C, d, D) => {

        // PHASE 1: instantiation
        let myClass = context.kernel.get<MyClass>("MyClass"); // a and b are injected

        // PHASE 2: initialization
        myClass.init(c, d); // c and d are initialized

        return myClass;

    }
);

I hope it helps and please share your ideas on the GitHub issues if you think you know a better way.

Upvotes: 5

Related Questions