Closed davidstellini closed 8 years ago
Hi, can you please try the following:
@injectable() // Needs to be injectable
class ApiDogDataRepository extends DataRepository<Dog> {
constructor(
@inject('IParser<Dog>') parser : IParser<Dog>
) {
super();
this.parser = parser;
}
}
@injectable() // Needs to be injectable
class ApiHumanDataRepository extends DataRepository<Human> {
constructor(
@inject('IParser<Human>') parser : IParser<Human>
) {
super();
this.parser = parser;
}
}
let kernel = new Kernel();
kernel.bind<DataRepository<Human>>("DataRepository<Human>")
.to(ApiHumanDataRepository);
kernel.bind<DataRepository<Dog>>("DataRepository<Dog>")
.to(ApiDogDataRepository);
kernel.bind<IParser<Human>>("IParser<Human>")
.to(ApiParser);
kernel.bind<IParser<Dog>>("IParser<Dog>")
.to(ApiParser);
I haven't run this code so I need your confirmation to know if it works :wink:
The following should also work:
// ...
@injectable() // Needs to be injectable
class ApiDogDataRepository extends DataRepository<Dog> {
constructor(
@inject('IParser') parser : IParser<Dog>
) {
super();
this.parser = parser;
}
}
@injectable() // Needs to be injectable
class ApiHumanDataRepository extends DataRepository<Human> {
constructor(
@inject('IParser') parser : IParser<Human>
) {
super();
this.parser = parser;
}
}
let kernel = new Kernel();
kernel.bind<DataRepository<Human>>("DataRepository<Human>")
.to(ApiHumanDataRepository);
kernel.bind<DataRepository<Dog>>("DataRepository<Dog>")
.to(ApiDogDataRepository);
kernel.bind<IParser<any>>("IParser").to(ApiParser);
If your ApiHumanDataRepository
and ApiDogDataRepository
are doing the same they could also become a generic type ApiDataRepository
:
// ...
@injectable()
class ApiDataRepository<T> extends DataRepository<T> {
constructor(
@inject('IParser') parser : IParser<T>
) {
super();
this.parser = parser;
}
}
let kernel = new Kernel();
kernel.bind<DataRepository<any>>("IDataRepository").to(ApiDataRepository);
kernel.bind<IParser<any>>("IParser").to(ApiParser);
let apiHumanDataRepository = kernel.get<DataRepository<Human>>("IDataRepository");
let apiDogDataRepository = kernel.get<DataRepository<Dog>>("IDataRepository");
Hi @Davste93 please confirm if this issue can be closed when you have a chance 😉
@remojansen Thanks! that clears up a lot :) Can I choose to bind a particular parser to each different repository?
Ex: bind SomeParser1 (implements Parser) -> ApiDogDataRepository bind SomeParser2 (implements Parser) -> ApiHumanDataRepository
This would be like your Warrior example, but extending it to:
interface IWeapon {}
abstract class Soldier {
weapon : IWeapon;
constructor(
@inject('Weapon') weapon : IWeapon;
) {
super();
this.weapon = weapon;
}
}
class Archer extends Soldier {}
class Knight extends Soldier {}
@injectable()
class Sword implements IWeapon{}
@injectable()
class Bow implements IWeapon{}
@injectable()
class DefaultWeapon implements IWeapon{}
kernel.bind<IWeapon>("IWeapon").to(DefaultWeapon);
//And then,
kernel.bind /*Knight.weapon */ to Sword
kernel.bind /*Archer.weapon */ to Bow
I want to bind it in a way that getting an instance of knight will create an instance of Sword, however knight should have no knowledge on how to create the Sword instance (thus the use of inversify and not type instantiation via constructor). I might choose to bind 'Mace' instead so that every time I create an instance of a knight it would have a mace Weapon.
My particular real world scenario is that I can't always use the same parser across multiple data repositories, but at the same time, most of the time it IS the same parser. So I can have a JSON parser used across 95% of the app, but 5% would use a HATEOAS parser.
What you are looking for is a feature called contextual bindings:
interface IWeapon {}
abstract class BaseSoldier {
weapon : IWeapon;
constructor(
weapon : IWeapon
) {
this.weapon = weapon;
}
}
@injectable()
class Soldier extends BaseSoldier {
constructor(
@inject('IWeapon') weapon : IWeapon
) {
super(weapon);
}
}
@injectable()
class Archer extends BaseSoldier {
constructor(
@inject('IWeapon') weapon : IWeapon
) {
super(weapon);
}
}
@injectable()
class Knight extends BaseSoldier {
constructor(
@inject('IWeapon') weapon : IWeapon
) {
super(weapon);
}
}
@injectable()
class Sword implements IWeapon{}
@injectable()
class Bow implements IWeapon{}
@injectable()
class DefaultWeapon implements IWeapon{}
let kernel = new Kernel();
kernel.bind<IWeapon>("IWeapon").to(DefaultWeapon).whenInjectedInto(Soldier);
kernel.bind<IWeapon>("IWeapon").to(Sword).whenInjectedInto(Knight);
kernel.bind<IWeapon>("IWeapon").to(Bow).whenInjectedInto(Archer);
kernel.bind<BaseSoldier>("BaseSoldier").to(Soldier).whenTargetNamed("default");
kernel.bind<BaseSoldier>("BaseSoldier").to(Knight).whenTargetNamed("knight");
kernel.bind<BaseSoldier>("BaseSoldier").to(Archer).whenTargetNamed("archer");
let soldier = kernel.getNamed<BaseSoldier>("BaseSoldier", "default");
let knight = kernel.getNamed<BaseSoldier>("BaseSoldier", "knight");
let archer = kernel.getNamed<BaseSoldier>("BaseSoldier", "archer");
console.log(knight.weapon);
console.log(archer.weapon);
I had to change your code because it is not possible to create an instance of an abstract class. This means that you will never be able to do something like:
kernel.bind<X>("X").to(SomeAbstractClass).
Also there is a limitation that forces you to indicate the injections in the derived class not in the abstract class so the following won't work:
abstract class BaseSoldier {
weapon : IWeapon;
constructor(
@inject('IWeapon') weapon : IWeapon
) {
this.weapon = weapon;
}
}
@injectable()
class Soldier extends BaseSoldier {}
But the following will work:
abstract class BaseSoldier {
weapon : IWeapon;
constructor(
weapon : IWeapon
) {
this.weapon = weapon;
}
}
@injectable()
class Soldier extends BaseSoldier {
constructor(
@inject('IWeapon') weapon : IWeapon
) {
super(weapon);
}
}
So you should be able to do what you want using contextual constraints but I have discovered a bug. If you decorate with injectable the abstract class:
@injectable()
abstract class BaseSoldier {
weapon : IWeapon;
constructor(
@inject('IWeapon') weapon : IWeapon
) {
this.weapon = weapon;
}
}
A friendly error is displayed:
Error: Derived class must explicitly declare its constructor: Soldier.
But if you forget @injectable()
undefined is infected and there are no errors displayed. I'm going to create a new issue to investigate this.
I have created the issue https://github.com/inversify/InversifyJS/issues/212
Found another way, this solves this issue for me: It compiles this time! :P Thanks for the beautiful library, I enjoy using it!
import {injectable, inject, Kernel, IKernel, IRequest} from "inversify";
import "reflect-metadata";
interface Parser<T>{
parse() : string;
}
class Dog {
name : string;
}
class Human{
surname : string;
}
@injectable()
class HateosParser<T> implements Parser<T> {
parse() : string {
return "Hello HATEOAS!";
}
}
@injectable()
class ApiParser<T> implements Parser<T> {
parse() : string {
return "Hello JSON!";
}
}
abstract class DataRepository<T> {
parser : Parser<T>;
}
@injectable()
class DogDataRepository extends DataRepository<Dog> {
constructor(
@inject('Parser') parser : Parser<Dog>
) {
super();
this.parser = parser;
}
}
@injectable()
class HumanDataRepository extends DataRepository<Human> {
constructor(
@inject('Parser') parser : Parser<Human>
) {
super();
this.parser = parser;
}
}
let kernel : IKernel = new Kernel();
kernel.bind<DogDataRepository>("DogDataRepository").to(DogDataRepository);
kernel.bind<HumanDataRepository>("HumanDataRepository").to(HumanDataRepository);
kernel.bind<Parser<any>>("Parser").to(HateosParser).when((request: IRequest) => {
return request.parentRequest.serviceIdentifier === 'DogDataRepository';
});
kernel.bind<Parser<any>>("Parser").to(ApiParser).when((request: IRequest) => {
return request.parentRequest.serviceIdentifier !== 'DogDataRepository';
});
var dogDataRepository = kernel.get<DogDataRepository>("DogDataRepository");
var humanDataRepository = kernel.get<HumanDataRepository>("HumanDataRepository");
console.log(dogDataRepository.parser.parse());
console.log(humanDataRepository.parser.parse());
Great :tada: I was about to send another example a bit more simple:
let kernel = new Kernel();
kernel.bind<IWeapon>("IWeapon").to(DefaultWeapon).whenInjectedInto(Soldier);
kernel.bind<IWeapon>("IWeapon").to(Sword).whenInjectedInto(Knight);
kernel.bind<IWeapon>("IWeapon").to(Bow).whenInjectedInto(Archer);
kernel.bind<BaseSoldier>("Soldier").to(Soldier);
kernel.bind<BaseSoldier>("Knight").to(Knight);
kernel.bind<BaseSoldier>("Archer").to(Archer);
let soldier = kernel.get<BaseSoldier>("Soldier");
let knight = kernel.get<BaseSoldier>("Knight");
let archer = kernel.get<BaseSoldier>("Archer");
console.log(soldier.weapon);
console.log(knight.weapon);
console.log(archer.weapon);
The thing about contextual bindings is that is a really flexible feature so it is easy to find alternative and better ways :smile:
Can I close this issue then?
Go ahead! Thank you!
This is still the case, unless skipBaseClassChecks
is set to true. Is there a way to tag specific class to be ignored from checks?
I'm trying to find out how to inject a specific class for a generic interface that are similar to some examples above, but I think that this is not possible. Any help would be much appreciate it.
interface DataProvider<T> {}
@injectable()
class StringDataProvider implements DataProvider<String> {}
@injectable()
class NumberDataProvider implements DataProvider<Number> {}
@injectable()
class DataWrapper<T> {
constructor(dataProvider: DataProvider<T>) {}
}
// expecting this to work to create DataWrapper object with NumberDataProvider injected
container.get<DataWrapper<Number>>(SomeClassifier)
// expecting this to work to create DataWrapper object with StringDataProvider injected
container.get<DataWrapper<String>>(SomeClassifier)
I've tried what it's been already discussed here but my use case seems different. This comment always returns ApiParser
and it's not what I want in my example.
What I don't understand is why this is ambiguous:
container.bind<DataProvider<Number>>("dataprovider").to(NumberDataProvider)
container.bind<DataProvider<String>>("dataprovider").to(StringDataProvider)
The classifier for the service is the same but the inferred types are differents.
Any help and/or guidance on this?
Hi,
I have another question on how to inject generics, possibly using a pattern similar to:
I've written a short example below: