Reputation: 3065
I have the following:
//Editor.TS
let values = await Promise.all([Theme.getTheme(config.theme), I18n.getI18n(config.i18n)]);
//Set up DI container
this.services = new Container();
decorate(injectable(), EventEmitter);
this.services.bind<IContext>(TYPES.Context).toConstantValue(this);
this.services.bind<Theme>(TYPES.Theme).toConstantValue(values[0]);
this.services.bind<I18n>(TYPES.I18n).toConstantValue(values[1]);
this.services.bind<any>(TYPES.Data).toConstantValue(data);
this.services.bind<Toolbox>(TYPES.Toolbox).to(Toolbox).inSingletonScope();
this.services.bind<Workspace>(TYPES.Workspace).to(Workspace).inSingletonScope();
A call to this.services.get<I18n>(TYPES.I18n);
works perfefctly if it's in the same function as the code above. However, I have a plugin system where each plugin is built (with webpack) into it's own library. The idea is you pull in only the plugins you need so as not to bloat the core library with stuff people might never use.
Anyways so the plugin constructor looks like this:
//APlugin.ts
constructor(config, services: Container) {
this.currentLang = this.defaultLang = config.defaultLanguage;
this.supportedLangs = config.supportedLanguages;
this.i18n = services.get<I18n>(TYPES.I18n);
this.context = services.get<IContext>(TYPES.Context);
//Editor Events
this.context.on("dataGenerating", e => this.onDataGenerating());
this.context.on("dataGenerated", data => this.onDataGenerated(data));
}
As you can see, the plugin accepts an anonymous object and a Container
instance. The idea here is to allow the plugin designer to pull any dependencies they need from the container. Writing the dependencies directly in the constructor is not an option for reasons that i'd rather not go into.
The plugins are instantiated as follows:
//Editor.ts
loadPlugin(plugin): IEditorPlugin {
let instance;
if (Array.isArray(plugin))
instance = new plugin[0](plugin[1], this.services);
else
instance = new plugin[0](this.services);
this.plugins.push(instance);
if (typeof instance.getMenuDom === 'function')
this.workspace.addButton(instance.getMenuDom());
return instance;
}
Here, the plugin
parameter can be either a constructor
or [constructor, config: any]
So the issue is that when the plugin's constructor is running I get the following error:
Uncaught (in promise) Error: No matching bindings found for serviceIdentifier: Symbol(I18n)
Remember that the exact same call to get
, only in the same function as the container
is defined works just fine.
If I debug and break on this.i18n = services.get<I18n>(TYPES.I18n);
in APlugin.ts, I see:
So it looks like the container
is set up and working correctly, the _bindingDictionary
property contains a reference to TYPES.I18n
. I should also mention that this issue occurs with all bindings. Replacing the get<I18n>
with any other bound type generates the same error.
I did some digging and it appears the point of failure is in the getBindings
function:
The call to bindingDictionary.hasKey
fails even though it's clear that the result should be true
.
Upvotes: 1
Views: 2981
Reputation: 3065
The reason is because calling Symbol(..)
creates a unique symbol every time it is called regardless of the argument being the same or not.
The key was to use Symbol.for(..)
. This creates a symbol and stores it in the global symbol cache, or return that symbol if it already exists. This will survive across contexts.
Upvotes: 2