Inversifyjs: Classes as ID: Missing required @inject

Created on 7 Dec 2016  路  2Comments  路  Source: inversify/InversifyJS

Expected Behavior

With the following code:

import { Container, injectable } from 'inversify'
import 'reflect-metadata'


/* Interfaces */

interface IMqttClient {
  send(topic: string, message: string): boolean
}

interface IWebSocketServer {
  send(message: string): boolean
}

/* Concretions */

@injectable()
class MqttClient implements IMqttClient {
  send (topic: string, message: string) {
    console.log(`MQTT - ${topic}: ${message}`)
    return true
  }
}

@injectable()
class WebSocketServer implements IWebSocketServer {
  private _mqttClient: IMqttClient

  constructor (mqttClient: IMqttClient) {
    this._mqttClient = mqttClient
  }

  send (message: string) {
    console.log(`WebSocketServer - ${message}`)
    this._mqttClient.send('topic', 'message')
    return true
  }
}

/* IoC */

const container = new Container()
container.bind<IMqttClient>(MqttClient).toSelf().inSingletonScope()
container.bind<IWebSocketServer>(WebSocketServer).toSelf().inSingletonScope()

const wss = container.get<IWebSocketServer>(WebSocketServer)
wss.send('lol')

And given the fact that the Support for classes pages states:

The @inject decorator is not required when you use classes

and given the fact that I did import reflect-metadata and set emitDecoratorMetadata to true, I would expect the above code to work.

Current Behavior

Error: Missing required @inject or @multiInject annotation in: argument 0 in class WebSocketServer.

I am very new to IoC in JS, but I am not seeing anything obviously wrong with the above code. Sorry if the problem seems obvious to you.

question

Most helpful comment

I first started with using Symbols as IDs and it worked great, but I wanted to see if I could simplify the flow of having to specify IMqttClient, Symbol('IMqttClient'), MqttClient. So I thought using the class as the ID would simplify everything. I also thought the IoC principle could be kept, e.g. by doing something like that in the context of a test for example:

container.bind<IMqttClient>(MqttClient).to(MqttClientShim).inSingletonScope()

And that it would be smart enough to guess the MqttClient or MqttClientShim depending of the context would "fit" into the IMqttClient contructor parameter. That's a lot of bad thoughts!

Anyway, thanks for your extensive answer, and for your great project. That's definitely a game changer.

All 2 comments

Hi! I think I know the solution.

The "quick and dirty" solution

Bindings are like dictionary entries. A dictionary entry has a key and a value. In a binding, the key can be a symbol, string or class. The value can be a lot of things: class, factory, constant or dynamic value, provider...

You have declared some bindings:

container.bind<IMqttClient>(MqttClient).toSelf().inSingletonScope();
container.bind<IWebSocketServer>(WebSocketServer).toSelf().inSingletonScope();

You have used the classes as key because the bind<T>(ID) method takes an ID as its argument. You also used the classes as value, because toSelf() uses the value of the key as the value itself.

At this point TypeScript knows how to resolve a request for two keys:

  • MqttClient
  • WebSocketServer

The problem is that your class WebSocketServer requires an interface:

constructor (mqttClient: IMqttClient) {
    this._mqttClient = mqttClient
}

You can solve it using the class instead of the interface:

constructor (mqttClient: MqttClient) {
    this._mqttClient = mqttClient
}

The recommended solution

Using classes as keys is not recommended because you lose the benefits of the inversion of control principle. Your classes are coupled to each other. You need to use interfaces as keys to be able to decouple it:

constructor (@inject("IMqttClient") mqttClient: IMqttClient) {
    this._mqttClient = mqttClient
}

As you can see interfaces require an inject annotation. This is the error that you have been seeing:

Error: Missing required @inject or @multiInject annotation in: argument 0 in class WebSocketServer.

Because the key is not the class you also need to update the bindings:

container.bind<IMqttClient>("IMqttClient").to(MqttClient).inSingletonScope();
container.bind<IWebSocketServer>("IWebSocketServer").to(WebSocketServer).inSingletonScope();

At that point you should be able to request intances of "IWebSocketServer":

const wss = container.get<IWebSocketServer>("IWebSocketServer");
wss.send('lol');

I first started with using Symbols as IDs and it worked great, but I wanted to see if I could simplify the flow of having to specify IMqttClient, Symbol('IMqttClient'), MqttClient. So I thought using the class as the ID would simplify everything. I also thought the IoC principle could be kept, e.g. by doing something like that in the context of a test for example:

container.bind<IMqttClient>(MqttClient).to(MqttClientShim).inSingletonScope()

And that it would be smart enough to guess the MqttClient or MqttClientShim depending of the context would "fit" into the IMqttClient contructor parameter. That's a lot of bad thoughts!

Anyway, thanks for your extensive answer, and for your great project. That's definitely a game changer.

Was this page helpful?
0 / 5 - 0 ratings