Inversifyjs: Property injection support

Created on 3 May 2016  ·  1Comment  ·  Source: inversify/InversifyJS

Expected Behavior

Be able to inject dependencies into properties:

class SomeService {}

class SomeWebComponent {
    @inject(SomeService)
    private _service: SomeService;
    public doSomething() {
        let count =  this._service.count;
        this._service.increment();
        return count;
    }
}

In a property injection the following:

@inject(SomeService)

Is identical to:

kernel.get<any>(SomeService)

Just like the bind method:

interface IKernel {
    bind<T>(serviceIdentifier: (string|Symbol|INewable<T>)): IBindingToSyntax<T>;
    // ...
}

It should allow string, Symbol and INewable<T> as identifier:

let ISomeService = "ISomeService";

class SomeWebComponent {
    @inject(ISomeService)
    private _service: ISomeService;
    public doSomething() {
        let count =  this._service.count;
        this._service.increment();
        return count;
    }
}

// or 

let ISomeService = Symbol("ISomeService");

class SomeWebComponent {
    @inject(ISomeService)
    private _service: ISomeService;
    public doSomething() {
        let count =  this._service.count;
        this._service.increment();
        return count;
    }
}

Named and tagged bindings should be supported if possible:

kernel.bind<IWeapon>(IWeapon).to(Shuriken).whenTargetNamed("throwable");
kernel.bind<IWeapon>(IWeapon).to(Katana).whenTargetNamed("not-throwable");

class Warrior {
    // AKA kernel.getNamed<any>(IWeapon, "throwable")
    @injectNamed(IWeapon, "throwable")
    @named("throwable")
    private _weapon: IWeapon;
    public fight() {
        this._weapon.use();
    }
}
kernel.bind<IWeapon>(IWeapon).to(Shuriken).whenTargetTagged("throwable", true);
kernel.bind<IWeapon>(IWeapon).to(Katana).whenTargeTagged("throwable", false);

class Warrior {
    // AKA kernel.getTagged<any>(IWeapon, "throwable", true)
    @injectTagged(IWeapon, "throwable", true)
    @tagged("throwable", true)
    private _weapon: IWeapon;
    public fight() {
        this._weapon.use();
    }
}

We also need @multiInject as a property decorator:

let inject = makePropertyInjectDecorator(kernel);
let multiInject = makePropertyMultiInjectDecorator(kernel);

class Warrior {
    @inject(Katana)
    public katana: Katana;
}

class Warrior {
    @multiInject(IWeapon)
    public weapons: IWeapon[];
}

It should support contextual bindings:

kernel.bind<IWeapon>("IWeapon").to(Katana).when((request: IRequest) => {
    return request.target.name.equals("_primaryWeapon");
});

kernel.bind<IWeapon>("IWeapon").to(Shuriken).when((request: IRequest) => {
    return request.target.name.equals("_secondaryWeapon");
});

class Ninja {
     // AKA kernel.get<any>(IWeapon)
    @inject(IWeapon)
    @paramName("_primaryWeapon") // rename to @targetName ?
    private _primaryWeapon: IWeapon;
    public fight() {
        this._primaryWeapon.use();
    }
}

It is likely that the property injection context will look as the following:

--- Context
     --- Kernel
     --- Request
          --- ID = IWeapon
          --- TARGET
               --- NAME = "_weapon"
               --- SERVICE = IWeapon

We can't add a parent request (Ninja) to the context because then the resolver will try to resolve the ninja (as opposed to resolve the weapon).

This means that only a few contextual constraints will be supported by property injection:

interface IBindingWhenSyntax<T> {
    when(constraint: (request: IRequest) => boolean): IBindingOnSyntax<T>; // OK
    whenTargetNamed(name: string): IBindingOnSyntax<T>; // OK
    whenTargetTagged(tag: string, value: any): IBindingOnSyntax<T>; // OK
    whenInjectedInto(parent: (Function|string)): IBindingOnSyntax<T>; // NOT SUPPORTED
    whenParentNamed(name: string): IBindingOnSyntax<T>; // NOT SUPPORTED
    whenParentTagged(tag: string, value: any): IBindingOnSyntax<T>; // NOT SUPPORTED
    whenAnyAncestorIs(ancestor: (Function|string)): IBindingOnSyntax<T>; // NOT SUPPORTED
    whenNoAncestorIs(ancestor: (Function|string)): IBindingOnSyntax<T>; // NOT SUPPORTED
    whenAnyAncestorNamed(name: string): IBindingOnSyntax<T>; // NOT SUPPORTED
    whenAnyAncestorTagged(tag: string, value: any): IBindingOnSyntax<T>; // NOT SUPPORTED
    whenNoAncestorNamed(name: string): IBindingOnSyntax<T>; // NOT SUPPORTED
    whenNoAncestorTagged(tag: string, value: any): IBindingOnSyntax<T>; // NOT SUPPORTED
    whenAnyAncestorMatches(constraint: (request: IRequest) => boolean): IBindingOnSyntax<T>; // NOT SUPPORTED
    whenNoAncestorMatches(constraint: (request: IRequest) => boolean): IBindingOnSyntax<T>; // NOT SUPPORTED
}

Multiple properties could be injected:

export class Settings extends Component<SettingsProps, {}> {
  @inject(MyService)
  private myService: MyService;

  @inject(MyService)
  private myService2: MyService;

  render() {}
}

Each property will have its own instance.

Current Behavior

Property injection is not supported.

Possible Solution

We need to create a new @inject property decorator. This decorator needs access to a kernel:

let kernel = new Kernel();
kernel.bind<SomeService>(SomeService).to(SomeService);
let inject= makeInjectPropertyDecorator(kernel);

Context

This feature was proposed by @otbe on #187

minor

Most helpful comment

Property injection is now available in [email protected] ⭐ 🎉 documentation is available here.

>All comments

Property injection is now available in [email protected] ⭐ 🎉 documentation is available here.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

codyjs picture codyjs  ·  3Comments

rashtao picture rashtao  ·  3Comments

hexa00 picture hexa00  ·  3Comments

AlexanderKiriluyk picture AlexanderKiriluyk  ·  4Comments

inaiei picture inaiei  ·  4Comments