inversify / InversifyJS

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

@lazyInject without statically defined containers #595

Closed ghost closed 7 years ago

ghost commented 7 years ago

I would like to "donate" AntidoteJS to this project. It was a quick prototype of an IoC container that works with React components without the need to define any custom annotations. If I can get a grip on the source code, I'll create a draft of a pull request that implements this feature, so you can see for yourself.

Expected Behavior

The following code should run:

import * as React from "react"
import { render } from "react-dom"
import { injectable, inject, Kernel } from "inversify"

@injectable()
export class MyApp extends React.Component {

  @inject(Types.Logger)
  logger: Logger

  render() {
    return <div>Woo this is cool!</div>
  }

}

const myContainer = new Container()
myContainer.bind<Logger>(Types.Logger).to(ConsoleLogger)

render(
  <Provider container={myContainer}>
    <MyApp />
  </Provider>
, document.getElementById('root'))

Current Behavior

The code above is invalid with the current build.

Possible Solution

Using a Proxy, the React component is wrapped into a virtual structure that upon construction will inject the necessary container coming from a <Provider /> that sits somewhere up the tree. It works surprisingly well, I have to say, and should work in most modern browsers.

Context

I'm setting up a new web platform, and it would be great if I could make use of InversifyJS instead of my current quickly-hacked-together package.

ghost commented 7 years ago

I just thought of something: this requires you to either tell InversifyJS upfront if it should make use of the Proxy method, or that it should autodetect whether to use this feature. In the second case, a dependency on React is required, which is probably a no-go. Maybe just like a lazyInjectable, there could be a hardInjectable which uses this proxy method? Something like this:

import { hardInjectable, inject } from "inversify"

@hardInjectable()
export class MyApp extends React.Component {

  @hardInject(Types.Logger)
  logger: Logger

  render() {
    return <div>Woo this is cool!</div>
  }

}

Not even sure the hardInject is necessary, because the method only needs to transform the class itself to a Proxy for it to work.

Also noting that there is a third option, where Proxy-based injection becomes the default for all objects as long as typeof Proxy !== 'undefined'. Actually, this is the one I would go for. I'll make this a separate issue.

remojansen commented 7 years ago

Hi, thanks for this suggestion. Are you aware of @lazyInject? It uses a proxy already. I don't see how @hardInjectable would be different?

ghost commented 7 years ago

Are you serious? I'm sorry, I thought it just bound the container to the annotation using some prototype magic. Then the issue I'm actually having is something entirely different: being able to use @inject from the package inversify directly instead of some custom defined annotation. It is needed in my case because I can't get access to the container beforehand (I'm defining a pluggable architecture, and the containers aren't statically defined).

I was also about to add that it won't work the way I described: you need a custom @injectable annotation which will wrap the component in a ProviderConsumer (this is what the @bind does in my container mini-library) .

remojansen commented 7 years ago

I think this is a very specific use case, it should not be part of the core module but I can show you a pattern to get around it:

  1. Create an internal instance of Container. This instance is internal to your library and it is not exposed.
  2. Create a lazyInject annotation using the Container instance defined in 1
  3. Allow the users of your library to pass their own Container instance to your library. You can then use container.merge or userContainer.parent = internalContainer to link the two containers.
ghost commented 7 years ago

Perfect, thanks! I'll try it later this evening or tomorrow and let you know how it went.

ghost commented 7 years ago

@remojansen All right, I got it working in this project 😄 Now I'm wondering if it will also work with unmanaged classes like React.Component, but that's for another project and another day.

remojansen commented 7 years ago

Glad to hear ti worked :smile_cat: