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.
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.
Hi! I think I know the 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:
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
}
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.
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 specifyIMqttClient,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:And that it would be smart enough to guess the
MqttClientorMqttClientShimdepending of the context would "fit" into theIMqttClientcontructor 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.