InversifyJS icon indicating copy to clipboard operation
InversifyJS copied to clipboard

property injection not working in React

Open Laazarus opened this issue 7 years ago • 18 comments

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",

Laazarus avatar Jan 02 '19 17:01 Laazarus

Same problem. Any idea?

ValorLin avatar Jan 07 '19 09:01 ValorLin

@weilao I tried everything I could and I found on the internet but I could not make it working...

I ended up using another library for now that used inversifyjs behind the scene but i really would like to use this method because it feels like more official...

I did not have any luck so far though.

Let me know if you want to know the library I am using (that one works)but as said i really would prefer using this the method describe here

Also if you find the solution I would really appreciate if you could share it :)

Regards

Laazarus avatar Jan 07 '19 09:01 Laazarus

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;
  }
}

ValorLin avatar Jan 15 '19 09:01 ValorLin

Use a workaround to make the inject work.

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

ValorLin avatar Jan 18 '19 08:01 ValorLin

Thanks, it is a shame you cannot use the normal injection :(

On 18 Jan 2019 08:03, 威老 [email protected] wrote:

Use a workaround to make the inject work.

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

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/inversify/InversifyJS/issues/1026#issuecomment-455459648, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AFdH2qkauahQCj1B-BLBuHAzVGvOCgZiks5vEX--gaJpZM4ZnAFa.

Laazarus avatar Jan 18 '19 09:01 Laazarus

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```

yuriCarlos avatar Jan 30 '19 11:01 yuriCarlos

Any updates on this?

EugenePisotsky avatar Feb 16 '19 23:02 EugenePisotsky

Seems related to #1050.

EDIT: Oh, I did not see you were the author of the related issue ^^

romain-faust avatar Mar 31 '19 09:03 romain-faust

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();
  };
}

k-g-a avatar Jun 24 '19 09:06 k-g-a

Any update on this?

thomai-d avatar Apr 22 '20 16:04 thomai-d

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

Strnadj avatar Jul 22 '20 06:07 Strnadj

Any update on this?

ektomorfik avatar Sep 07 '20 15:09 ektomorfik

Any update on this?

beephotography avatar Nov 10 '20 08:11 beephotography

Any update on this?

iGroza avatar May 04 '21 04:05 iGroza

Hello, we are currently in a major refactoring. I am sorry we don't have time for all the issues, We will try to focus on making v6 stable ( focus on making typescript compiler more strict, add "proper" typings, refaactor a lot of the code, fix multiple issues. ... ). We will focus on issues after that is at least green. ( we have issues that are so old ( 2018 ) that they are probably not even replicable anymore ).

Any help is much appreciated.

PodaruDragos avatar May 04 '21 10:05 PodaruDragos

Any update on this??

Pines-Cheng avatar Nov 23 '21 12:11 Pines-Cheng

no updates, but feel free to try a PR for this

PodaruDragos avatar Nov 23 '21 12:11 PodaruDragos

I also have same issue, solved with:



const createActivityStoreClass=(activityService: ActivityService)=>{

  @injectable()
  class ActivityStore{
    public all?:ActivityDto[];
    public activityService=activityService
  
    constructor(){
      makeObservable(this,{
        all: observable,
        delete: action,
        load: action,
        create: action,
      })
      this.load();
    }
  
    load(){
      this.activityService.getAll().then((data)=>{
        this.all=data;
      })
    }
  
    async create(data:CreateActivity){
      return await this.activityService.create(data).then((res)=>{
        this.all?.unshift(res)
        return res;
      });
    }
  
  
    async delete(id:number){
      return await this.activityService.delete(id).then((res)=>{
        this.all = this.all?.filter((item) => item.id !== id);
        return res
      });
    }
  }

  return ActivityStore
}

export default class ActivityStore extends createActivityStoreClass(new ActivityService()){}

soekasir avatar Oct 10 '22 12:10 soekasir