Inversifyjs: [inversify-binding-decorators] Cannot apply @provide decorator multiple times

Created on 13 Nov 2017  路  10Comments  路  Source: inversify/InversifyJS

Expected Behavior

In inversify-binding-decorators. The following should NOT throw an exception Cannot apply @injectable decorator multiple times:

@provide("SomeInterface")
@provide("SomeOtherInterface", true) // new argument!!
class SomeClass {
    // ....
}

The new argument allows us to disable the exception.

Current Behavior

The following throws an exception Cannot apply @injectable decorator multiple times:

@provide("SomeInterface")
@provide("SomeOtherInterface")
class SomeClass {
    // ....
}

Possible Solution

1. Send PR to InversifyJS

export { Container } from "./container/container";
export { ContainerModule } from "./container/container_module";
export { injectable } from "./annotation/injectable";
export { tagged } from "./annotation/tagged";
export { named } from "./annotation/named";
// ...
import * as METADATA_KEY from "./constants/metadata_keys.ts";
export { METADATA_KEY };

2. Send PR to inversify-binding-decorators

import { decorate, injectable, METADATA_KEY } from "inversify";
import { interfaces } from "inversify";

function provide(container: interfaces.Container) {

  // function is named for testing
  return function _provide(
    serviceIdentifier: interfaces.ServiceIdentifier<any>,
    force?: boolean
  ) {
    let bindingWhenOnSyntax = container.bind<any>(serviceIdentifier).to(<any>null);
    return function (target: any) {
      if (
         force === true &&
         Reflect.hasOwnMetadata(METADATA_KEY.PARAM_TYPES, target) === false
      ) {
         decorate(injectable(), target);
      } else {
         try {
             decorate(injectable(), target);
         } catch(e) {
             throw new Error(`
                 ${e.message}
                 Please use @provide("ServiceIdentifier", true) if
                 you are trying to declare multiple bindings!
             `)
         }
      }
      let binding: interfaces.Binding<any> = (<any>bindingWhenOnSyntax)._binding;
      binding.implementationType = target;
      return target;
    };
  };
}

export default provide;

Steps to Reproduce (for bugs)

Decorate multiple times with @provide a class:

@provide("SomeInterface")
@provide("SomeOtherInterface")
class SomeClass {
    // ....
}

Context

This issue is a response to #53

Accepting PRs Your first PR

Most helpful comment

All 10 comments

One of the :fire: and most requested features for us!

@PowerMogli would you like to try to send a PR, one of the reasons I didn't do it already is because it is an easy change and I was waiting for someone that maybe wants to contribute for the first time...

Ah cool. You fixed it very fast. But unfortunately we use makeFluentProvideDecorator to generate a @provideFluent instead of @provide. The reason is we have a function defined like this:

const container = new Container();

const provideFluent = makeFluentProvideDecorator(container);

const provideSingleton = (identifier: any) => {
  return provideFluent(identifier)
    .inSingletonScope()
    .done();
};

Do you have any tip how we can apply your commit to makeFluentProviderDecorator?

It works great, but we see a big problem: providing the decorator multiple times, inversify creates every time a new instance of the decorated class. Our intention was to generate only one instance of the class but decorated by multiple different identifiers.

Lets consider the following example:

@provideSingleton(VIEWSTATE.ViewStateSynchronizer)
@provideSingleton(TYPE.PatientComponentModel)
export class PatientComponentModel extends BaseComponentModel<IPatient>, IViewStateSynchronizer {
....
}

export class PatienComponent {
@lazyInject(TYPE.PatientComponentModel) patientComponentModel: PatientComponentModel;
....
}

export class DataSync {
constructor(@multiInject(VIEWSTATE.ViewStateSynchronizer) private viewStateSynchronizers: IViewStateSynchronizer[]){}
...
}

DataSyns service and the PatientComponent (Angular 2) have to get the same instance of PatientComponentModel injected. But both have to inject it by a different identifier.

I hope I could explain it clear enough and you can help us out here (again) 馃憤

@PowerMogli I think I get you. Do you mind creating a separate issue, please? This feature could be complicated.

We probably want something like:

@provideSingleton(VIEWSTATE.ViewStateSynchronizer, TYPE.PatientComponentModel)

But that would require:

container.bind(VIEWSTATE.ViewStateSynchronizer, TYPE.PatientComponentModel)
         .to(PatientComponentModel)

Which I'm not sure about being possible. Let me think about this one because I think I can come up with a workaround. I will get back to you ASAP

@PowerMogli Did an issue ever get raised to cover your scenario above. I am trying to accomplish exactly the same thing and am observing the same behavior where my "singleton" is created twice, once for each binding.

@remojansen Did you ever come up with the workaround, would be great if you could share if you did.

@Recodify Sorry, we did not raise an issue. Just because we moved away from InversifyJS to Angulars own DI with 'multiple' feature. And Angular had big problems when building our project with InversifyJS and in production mode.

Was this page helpful?
0 / 5 - 0 ratings