Inversifyjs: Support bindings of one service identifier to another (transitive bindings)

Created on 17 Jun 2017  路  6Comments  路  Source: inversify/InversifyJS

I have the following case:

bind(WorkspaceFrontendContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toDynamicValue(ctx => 
  ctx.container.get(WorkspaceFrontendContribution)
).inSingletonScope();
bind(CommandContribution).toDynamicValue(ctx => 
  ctx.container.get(WorkspaceFrontendContribution)
).inSingletonScope();
bind(MenuContribution).toDynamicValue(ctx => 
  ctx.container.get(WorkspaceFrontendContribution)
).inSingletonScope();
bind(KeybindingContribution).toDynamicValue(ctx => 
  ctx.container.get(WorkspaceFrontendContribution)
).inSingletonScope();

, where WorkspaceFrontendContribution implements FrontendApplicationContribution, CommandContribution, MenuContribution and KeybindingContribution

I can shorten it using an array:

bind(WorkspaceFrontendContribution).toSelf().inSingletonScope();
for (const identifier of [FrontendApplicationContribution, CommandContribution, MenuContribution, KeybindingContribution]) {
  bind(identifier).toDynamicValue(ctx => 
    ctx.container.get(WorkspaceFrontendContribution)
  ).inSingletonScope();
}

But it would be nicer to have something like:

bind(WorkspaceFrontendContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(WorkspaceFrontendContribution);
bind(CommandContribution).toService(WorkspaceFrontendContribution);
bind(MenuContribution).toService(WorkspaceFrontendContribution);
bind(KeybindingContribution).toService(WorkspaceFrontendContribution);

, toService could be implemented more optimal when using toDynamicValue, e.g. toDynamicValue makes it harder to see why something cannot be bound, since a request inside toDynamicValue does not have an info about the parent request

Most helpful comment

This is now implemented by https://github.com/inversify/InversifyJS/pull/720 and released by 4.9.0 馃帀

All 6 comments

It would be nice if the following shortcut would be possible then:

bind(FrontendApplicationContribution).toService(WorkspaceFrontendContribution).toSelf().inSingletonScope();

, that should be an equivalent to:

bind(WorkspaceFrontendContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(WorkspaceFrontendContribution);

It is the very common case for us:

  • a framework is programmed against interfaces, e.g. FrontendApplicationContribution
  • a framework extension is programmed against implementations of framework interfaces, e.g. WorkspaceFrontendContribution
  • but both should work with the same instances

Basically, a framework extension should always use toDynamicValue for each implemented interface from a framework right now, that is a lot of code and issues with tracking down cycles and missing bindings.

Hi @akosyakov If you can do the following:

bind(WorkspaceFrontendContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(WorkspaceFrontendContribution);
bind(CommandContribution).toService(WorkspaceFrontendContribution);
bind(MenuContribution).toService(WorkspaceFrontendContribution);
bind(KeybindingContribution).toService(WorkspaceFrontendContribution);

It means that I would need to implement toService. The toService binding will use toDynamicValue under the hood.

Suggested multiBindToService helper

If I do that and I also create the following function:

const multiBindToService = (container) => (service) => (...types) => {
    return types.map(t => container.bind(t).toService(WorkspaceFrontendContribution));
};

You will also be able to do:

container.bind(WorkspaceFrontendContribution).toSelf().inSingletonScope();

multiBindToService(container)(WorkspaceFrontendContribution)(
    CommandContribution,
    MenuContribution,
    KeybindingContribution
);

How can I customize the scope with multiBindToService?

Because the helper returns an array of bindings you will be able to configure other options like for example inSingletonScope:

multiBindToService(container)(WorkspaceFrontendContribution)(
    CommandContribution,
    MenuContribution,
    KeybindingContribution
).map(b => b.inSingletonScope());

Why do you use multiple functions in multiBindToService?

I use multiple functions (container) => (service) => (...types) => to allow you to avoid repeating your self:

container.bind(WorkspaceFrontendContribution).toSelf().inSingletonScope();

// You can create partial functions if you expect to call 
// multiBindToService(container)(WorkspaceFrontendContribution)
// multiple times
const mbts = multiBindToService(container);
const mbtWorkspaceFrontendContribution = mbts(WorkspaceFrontendContribution);

mbtWorkspaceFrontendContribution(
    CommandContribution,
    MenuContribution,
    KeybindingContribution
);

mbtWorkspaceFrontendContribution(
    SomeType1,
    SomeType2,
    SomeType3
);

Summary

I can implement:

  • The toService feature
  • The multiBindToService function

And you will be able to do:

container.bind(WorkspaceFrontendContribution).toSelf().inSingletonScope();
container.bind(FrontendApplicationContribution).toService(WorkspaceFrontendContribution);
container.bind(CommandContribution).toService(WorkspaceFrontendContribution);
container.bind(MenuContribution).toService(WorkspaceFrontendContribution);
container.bind(KeybindingContribution).toService(WorkspaceFrontendContribution);

Or:

container.bind(WorkspaceFrontendContribution).toSelf().inSingletonScope();

multiBindToService(container)(WorkspaceFrontendContribution)(
    CommandContribution,
    MenuContribution,
    KeybindingContribution
);

Would that work?

Hi, yes it should work.

My main concern is support of chained bindings, toService should get it covered, like:

container.bind(MySqlDatabaseTransactionLog).toSelf().inSingletonScope(); // resolves to MySqlDatabaseTransactionLog
container.bind(DatabaseTransactionLog).toService(MySqlDatabaseTransactionLog); // resolves to MySqlDatabaseTransactionLog
container.bind(TransactionLog).toService(DatabaseTransactionLog); // resolves to MySqlDatabaseTransactionLog as well

Regarding to a shortcut to bind multiple identifiers at once:
I was wondering whether bind can support var-args generally, i.e.:

container.bind(WorkspaceFrontendContribution).toSelf().inSingletonScope();
container.bind(CommonContribution, MenuContribution, KeybindingContribution).toService(WorkspaceFrontendContribution);

If not the helper function is fine with me.

This is now implemented by https://github.com/inversify/InversifyJS/pull/720 and released by 4.9.0 馃帀

Cool! Thank you!

Was this page helpful?
0 / 5 - 0 ratings