InversifyJS: property injection not working in React

I am writing learning React and I wanted to use it inversifyjs as I have been using it for other projects. I have seen that in order to use it with react we have to use inversify-inject-decorators and I have followed the guide for this package but I cannot get the injection working

Expected Behavior

IoC.ts


import { Container} from "inversify";

import getDecorators from "inversify-inject-decorators";
import { GameServices } from "../providers/game/game.services";
import { IGameService } from "../providers/game/game.service.interfaces";

 const container = new Container();
 container.bind<IGameService>("gameServices").toConstructor(GameServices);
 const { lazyInject} = getDecorators(container);
 
export {lazyInject};

in GameServices

import { IGameService, IHistoryItem } from "./game.service.interfaces";
import { injectable } from "inversify";

@injectable()
export class GameServices implements IGameService {
  private readonly statistics: IHistoryItem[] = [];
  private moves: number = 0;
  calculateWinner(squares: string[]): string {
 //do something
    return "";
  }
  public retrieveStatistics(): IHistoryItem[] {
    return this.statistics;
  }
}

Game Component

class Game extends React.Component<{}, GameState> {
  @lazyInject("gameServices")
  private readonly gameServices: IGameService;
 public render() {
    const history = this.state.history;
    const current = history[this.state.stepNumber];
    console.log("calling the service", this.gameServices)
   const winner = this.gameServices.calculateWinner(current.squares);
return ();
}
}

This code should work

Current Behavior

I am not getting any compilation error but when the line

 const winner = this.gameServices.calculateWinner(current.squares);

gets executed I get an exception because gameService is undefined…

Environment

  • Version used:
  • inversify": “^5.0.1”,
  • “inversify-inject-decorators”: “^3.1.0”,
  • “typescript”: “^3.2.2”
  • “react”: “^16.7.0”,

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 9
  • Comments: 18 (2 by maintainers)

Most upvoted comments

Just found this issue while trying to solve the same problem for project using babel with plugin-proposal-decorators (legacy mode). My workaround is to define own decorators which exploit babel’s internal initializer (can read about it here) descriptor property to settle down prototype lookup:


const DECORATORS = getDecorators(myContainer);

interface IBabelPropertyDescriptor extends PropertyDescriptor {
  initializer(): any;
}

export const injectProperty = function(serviceIdentifier: interfaces.ServiceIdentifier<any>) {
  const original = DECORATORS.lazyInject(serviceIdentifier);
  // the 'descriptor' parameter is actually always defined for class fields for Babel, but is considered undefined for TSC
  // so we just hack it with ?/! combination to avoid "TS1240: Unable to resolve signature of property decorator when called as an expression"
  return function(this: any, proto: any, key: string, descriptor?: IBabelPropertyDescriptor): void {
    // make it work as usual
    original.call(this, proto, key);
    // return link to proto, so own value wont be 'undefined' after component's creation
    descriptor!.initializer = function() {
      return proto[key];
    };
  };
};
// same overrides go for other 3 lazy* decorators

Usage:

export class MyClassWithInjection extends React.Component<IMyProps, IMyState> {
  // here we recieve TS1240 error, if didn't use ?/! hack
  @injectProperty(foo) private _foo!: IFoo;
  //...
  private _invokeSomethingOnFoo = () => {
    this._foo.bar();
  };
}

Any update on this? Workarounds actually not working for us…

Use a workaround to make the inject work.

class Game extends React.Component {
  gameService: GameService = container.get(GameService);
}

In this week I started to learn about inversify with React too and I’m having the same issue… I got the basic example of https://github.com/inversify/inversify-inject-decorators and just pasted it on a new class in my project, just to test, and even the basic example was not working.

I was looking in the implementation, trying to find some guess of what was happening… This library (if I didnt get wrong) inject the properties on the prototype of the object, so the injection works only when you do a get on the property of prototype.

When you do a get on the object, the js will look at the instance to see if the property exists there. If it doesnt then Js will look at the instance’s prototype chain until get the value wanted.

The problem is that when you create a instance (new WhateverClass()) all properties will be in your instance, so js will never do a search on the prototype chain and, because of that, your instance will never be injected.

To exemplify what I’m talking about I got the basic example I said above and made some changes. That’s an example of what is happening here:

@dcavanagh and @remojansen, Do you have some guess about what is happening here?

import "reflect-metadata"

import getDecorators from "inversify-inject-decorators";
import { Container, injectable, tagged, named } from "inversify";

let container = new Container();
let { lazyInject } = getDecorators(container);
let TYPES = { Weapon: "Weapon" };

interface Weapon {
    name: string;
    durability: number;
    use(): void;
}

@injectable()
class Sword implements Weapon {
    public name: string;
    public durability: number;
    public constructor() {
        this.durability = 100;
        this.name = "Sword";
    }
    public use() {
        this.durability = this.durability - 10;
    }
}

class Warrior {
    constructor(){}
    @lazyInject(TYPES.Weapon)
    weapon: Weapon;
}

container.bind<Weapon>(TYPES.Weapon).to(Sword);

let warrior = new Warrior();
console.log(warrior.weapon instanceof Sword); // false

delete warrior.weapon

console.log(warrior.weapon instanceof Sword); // true

export default warrior```

As far as I know, it’s caused by @babel/plugin-proposal-class-properties.

The code above was compiled to something like:

class Game extends React.Component {
  constructor() {
     // This is why the injection broken.
     this.gameService = undefined;
  }
}