inversify / InversifyJS

A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.
http://inversify.io/
MIT License
11.22k stars 714 forks source link

property injection not working in React #1026

Open Laazarus opened 5 years ago

Laazarus commented 5 years ago

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

ValorLin commented 5 years ago

Same problem. Any idea?

Laazarus commented 5 years ago

@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

ValorLin commented 5 years ago

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 commented 5 years ago

Use a workaround to make the inject work.

class Game extends React.Component {
  gameService: GameService = container.get(GameService);
}
Laazarus commented 5 years ago

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

On 18 Jan 2019 08:03, 威老 notifications@github.com 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.

yuriCarlos commented 5 years ago

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```
EugenePisotsky commented 5 years ago

Any updates on this?

romain-faust commented 5 years ago

Seems related to #1050.

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

k-g-a commented 5 years ago

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();
  };
}
thomai-d commented 4 years ago

Any update on this?

Strnadj commented 4 years ago

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

ektomorfik commented 4 years ago

Any update on this?

beephotography commented 3 years ago

Any update on this?

iGroza commented 3 years ago

Any update on this?

PodaruDragos commented 3 years ago

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.

Pines-Cheng commented 2 years ago

Any update on this??

PodaruDragos commented 2 years ago

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

soekasir commented 2 years ago

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