inversify / InversifyJS

A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.
http://inversify.io/
MIT License
11.29k stars 716 forks source link

Instantiating a new class and omitting @injectable #409

Closed shlomiassaf closed 7 years ago

shlomiassaf commented 7 years ago

Currently, as I understand, there's no way to instantiate a class without

  @injectable() MyClass {};
  container.bind(MyClass).toSelf();
  const myClass = container.get(MyClass);

That seem's a lot of work for a simple (yet common) task. Another point is that not all classes should be @injectables, sometimes you just want to create a new instance of the class with automatic injections.

It should be a simple task of

  MyClass {};
  const myClass = container.instantiate(MyClass);

Is it possible? If "emitDecoratorMetadata": true we can get the types in the constructor... I don't know if TS will emit the type information for non-decorated parameters...

thoughts?

remojansen commented 7 years ago

The type information is only emitted when a decorator is applied :cry: Have you take a look to inversify-binding-decorators?

remojansen commented 7 years ago

The type information is only emitted when a decorator is applied :cry:

Have you tried inversify-binding-decorators?

shlomiassaf commented 7 years ago

Bummer :/

Yes I know inversify-binding-decorators, used it before but its a 70% solution... anyway no types no good :)

BTW, can you publish nversify-binding-decorators for 3.0.1? I saw it was commited but it's not on npm

Interesting thing... how are you going to manage bindings where multiple containers are an option?

remojansen commented 7 years ago

Sure! I will try my best to publish this evening but I just arrived from a week awake and I have to write back to many emails before :sob:

remojansen commented 7 years ago

About your question about multiple containers...

Lets say that you have two npm modules:

Both modules are then consumed from a third module C. But how can we get the Katana to be injected into the Ninja if the bindings and metadata are in two different containers. There are two available solutions:

shlomiassaf commented 7 years ago

@remojansen no worries, publish when you can :)

Thanks for the help! I will close this now.

pmoleri commented 7 years ago

Hi, I'm looking for something like what was mentioned here: const myClass = container.instantiate(MyClass);

Given that the class is decorated, it would be nice to have a way to construct an instance of without binding it to the container. Is there any way to do it?

Dirrk commented 7 years ago

@pmoleri the container is how inversify works.

If you want to get around doing the container.bind('MyClass').to(MyClass) then you can use inversify-binding-decorators as suggested in this thread.

If you want to get around creating an instance whenever you need one, consider setting up a factory and injecting that into the function you want to generate other objects from.

Or you could (although I think it goes against the whole idea of DI) and export a function from your inversify.config.ts like so:

const GetMyClassFactory = (identifier: string): MyClass => {
  return container.get<IClass>('MyClass');
}

export { GetMyClassFactory }
pmoleri commented 7 years ago

Hi @Dirrk,

Thanks for your input. I get that inversify-binding-decorators gives me a shortcut to auto bind implementations, but that's actually what I'm trying to avoid.

In this case, I want to use the container to construct an instance of B, but I don't want to get it registered in the container.

E.g.:

@injectable()
class A {
    prop = "test";
}

@injectable()
class B {
    constructor(private a: A) {
    }
}

const container = new Container();
container.bind(A).to(A);
container.get(B); // Error: No matching bindings found for serviceIdentifier: B

At this point, the container has all the information it needs to construct an instance of B.

I agree get should fail because it the binding wasn't registered. But another function such as container.create(class) could be implemented to enable users to create a class without register the binding.

As a workaround I could just bind/get/unbind, but I guess the create option may be a nice addition to the api.

I can create a new issue if you it’s worth it.

Dirrk commented 7 years ago

@pmoleri, thanks for bringing more light into the issue as I now have a much better understanding of what you want to do. However, I would like to know a use case for this before we pull anyone in.

For instance, using your example with A and B. Why would you not just bind B? And more importantly why do you need to unbind it to get the affect you want? Is there some use case, that needs you to use the container but can't have it bound? Have you seen used this feature in another langauge ?

Basically, it sounds like your trying to create a factory but don't want to create a factory. I would check these docs on how to create a factory and I would be your use case would fit perfectly in it. https://github.com/inversify/InversifyJS/blob/master/wiki/factory_injection.md

Hope this helps!

pmoleri commented 7 years ago

Hi @Dirrk,

Thanks again for taking the time to answer. It's not about wanting a factory or an instance, it's more about that I don't find necessary to bind just to execute a get.

In my use case, I'm loading classes at runtime (like plugins), these classes are @injectable so I'm using the container to create the instances, but I don't have any particular interest in binding them to the container.

Perhaps there's another way to structure the application, for example loading and binding the classes at startup and using multiInject, but I don't think I should restructure the application just to play nice with inversify. However I'll keep the idea alive, I may be missing something valuable and I don't see it just yet.

As for if I have seen it in another language. I think it's what Unity does:

public ManagementController(ITenantStore tenantStore)
{
  this.tenantStore = tenantStore;
}

var container = new UnityContainer();
container.RegisterType<ITenantStore, TenantStore>();

var controller = container.Resolve<ManagementController>();
Dirrk commented 7 years ago

@pmoleri, I think this is possible to create. The runtime limitations would be avoided because Resolve could then do the work needed to add it to the container without actually doing it (but still resolve the dependencies). I think its worth creating a new issue referencing the new information you have provided and write some rudimentary examples that you would expect to work using the Ninja / Katana / Sword type examples to keep with the theme.

pmoleri commented 7 years ago

Hi @Dirrk, I'll create the issue. Thanks for everything.