Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Failing to @inject into class constructor #1007

Closed
ryall opened this issue Nov 27, 2018 · 17 comments
Closed

Failing to @inject into class constructor #1007

ryall opened this issue Nov 27, 2018 · 17 comments

Comments

@ryall
Copy link

ryall commented Nov 27, 2018

I believe I have everything set up correctly but @inject is failing to inject into the class constructor with the error: Missing required @inject or @multiInject annotation in: argument 0 in class AccountFactory despite being defined on both properties.

Using @inject on class properties works as expected. I'm only having this problem with constructor params.

Expected Behavior

This should inject the correct service

@injectable()
export class AccountFactory {
  constructor(
    @inject($.MongoDb.Client) private mongo: MongoClient,
    @inject($.Security.PasswordHasher) private passwordHasher: IPasswordHasher,
  ) {}
}

Current Behavior

I get the error: Missing required @inject or @multiInject annotation in: argument 0 in class AccountFactory.

Types are definitely correct.

Property injection works as expected:

@injectable()
export class AccountFactory {
  @inject($.MongoDb.Client)
  private mongo: MongoClient;

  @inject($.Security.PasswordHasher)
  private passwordHasher: IPasswordHasher;
}

Possible Solution

Maybe I have configured something incorrectly, so here are the relevant files:

Inversify container definition:

export const container = new Container();

container.bind($.MongoDb.Client).toConstantValue(mongoClient);
container
  .bind<IPasswordHasher>($.Security.PasswordHasher)
  .to(BCryptPasswordHasher)
  .inSingletonScope();
container
  .bind($.MongoDb.Factory.Account)
  .to(AccountFactory)
  .inSingletonScope();

tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "lib": ["esnext", "dom"],
    "types": ["node", "jest", "reflect-metadata"],
    "module": "commonjs",
    "moduleResolution": "node",
    "noEmit": true,
    "pretty": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "allowJs": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": false,
    "noUnusedLocals": false,
    "strictNullChecks": true,
    "forceConsistentCasingInFileNames": true,
    "suppressImplicitAnyIndexErrors": true,
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
}

.babelrc:

{
  "presets": [["@babel/env"], ["@babel/typescript"]],
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ],
    [
      "@babel/plugin-proposal-class-properties",
      {
        "loose": true
      }
    ]
  ]
}

webpack.js:

const nodeExternals = require('webpack-node-externals');

const paths = require('./paths');

module.exports = {
  mode: 'production',
  entry: ['reflect-metadata', '@babel/polyfill', `${paths.src}/index.ts`],
  node: {
    process: false,
  },
  output: {
    filename: 'app.js',
    path: paths.build,
  },
  // Auto resolution
  resolve: {
    extensions: ['.ts', '.js'],
    modules: ['node_modules', `${paths.root}/node_modules`],
  },
  // File and Webpack module rules
  module: {
    rules: [
      {
        test: /\.[tj]s$/,
        include: paths.root,
        loader: 'babel-loader',
        options: {
          cacheDirectory: true,
        },
      },
    ],
  },
  externals: [
    nodeExternals(), // Link modules externally as we don't need to bundle for a server
  ],
};

Context

Without this, I cannot isolate units for testing without relying on Inversify rebinds.

Your Environment

Inversify: 5.0.1
reflext-metadata: 0.1.12

@arturgieralt
Copy link

I had the same issue when I was using babel. Use tsloader instead of it!
Here you can find my config files:
https://github.com/arturgieralt/realTimeGame

@yaitskov
Copy link

so did anybody open an issue in babel project?

@CharanRoot
Copy link

i faced similar issue. after importing reflect-metadata package fixed issue.

@mcshaz
Copy link

mcshaz commented May 8, 2019

I was having a similar issue in typescript with optional properties coming after the injected properties 'missing required @Inject on argument 2'. The solution for me was to explicitly assign undefined to the optional properties i.e.

constructor(@inject(TYPES.IFetch) updateProvider: IFetch,
            @inject(TYPES.ILogger) logger: ILogger,
            indexedDb?: IDBFactory, 

...
became

constructor(@inject(TYPES.IFetch) updateProvider: IFetch,
            @inject(TYPES.ILogger) logger: ILogger,
            indexedDb: IDBFactory | undefined = void 0,

as a small aside, ideally the error message would state arguments[2] rather than argument 2 - as this would be more explicit that it was referring to the index.

@wyzzy
Copy link

wyzzy commented May 9, 2019

I'm on RN 0.59.5, TS 3.4.5, Babel 7.4.4 (core, runtime & plugin-proposal-decorators). I had happily been using @injectable()as a class decorator for ages. When I introduced @inject in the parameter list of a class constructor for the first time, the 'standard' RN project recommendations for Babel & Metro config gave an Unexpected @ token error during the bundling process.

I had to switch to using react-native-typescript-transformer in my metro.config.js to resolve the problem.

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: false,
      },
    }),
    babelTransformerPath: require.resolve('react-native-typescript-transformer')
  },
};

@nsainaney
Copy link

I have set up a simple repo to illustrate this here: https://github.com/nsainaney/inversifyBabel

react-native-typescript-transformer does not seem to help. The codebase is quite simple:

// services.ts
import { Container } from 'inversify'

const kernel = new Container()

class Hello {
    sayHi() {
        console.log('Hi')
    }
}

class There {
    sayThere() {
        console.log('There')
    }
}


kernel.bind('hello').to(Hello)
kernel.bind('there').to(There)

// index.ts
import 'reflect-metadata'
import React from 'react'
import './services'
import { Text, View } from 'react-native';
import { injectable, inject } from 'inversify'

@injectable()
export default class App extends React.Component {

    @inject('hello')
    hello

    @inject('there')
    there

    componentDidMount() {
        this.hello.sayHi()              // CRASHes here as hello is undefined
        this.there.sayThere()
    }

  render() {
    return (
      <View>
        <Text>Open up App.js to start working on your app!</Text>
      </View>
    );
  }
}

@nsainaney
Copy link

nsainaney commented May 13, 2019

I was finally able to fix the above. I was missing:

  1. Use lazyInject instead of inject
  2. @injectable() was missing on class Hello and class There

I've updated the codebase here: https://github.com/nsainaney/inversifyBabel. Hope this helps others out

@victorkurauchi
Copy link

I've added what @wyzzy suggested and it worked fine.

  • "react-native": "0.61.4",
  • "@babel/plugin-proposal-decorators": "^7.7.0",
  • "@babel/core": "^7.7.2",

babel.config.js

module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['module:metro-react-native-babel-preset', 'module:react-native-dotenv'],
    plugins: [
      ['@babel/plugin-proposal-decorators', { 'legacy': true }],
    ]
  };
};

metro.config.js

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: false,
      },
    }),
    babelTransformerPath: require.resolve('react-native-typescript-transformer')
  },
};

@jgornick
Copy link

jgornick commented Jan 2, 2020

For anyone that runs into this, I solved my babel-only solution by using https://github.com/WarnerHooh/babel-plugin-parameter-decorator

@martppa
Copy link

martppa commented Mar 4, 2020

@ryall I think the problem here is just that Inversify doesn't support class member injection directly in constructor. If you want to inject in the constrcutor you have to turn it into normal params by removing the visibility modifier.

@am0wa
Copy link

am0wa commented Dec 2, 2020

Check the stackoverflow.com::babel-7-inversify-4-webpack-4-unexpected-character-on-inject answer.

babel-plugin-transform-typescript-metadata plugin helps.
Thx gods, guys made this plugin, wish it would be part of @babel/preset-typescript to work out of box.

@RyanGhd
Copy link

RyanGhd commented Jan 19, 2021

I had the same issue using constructor injection in a react app which was created using create-react-app (react 17, react-scripts 4).
this is how the issue was fixed for me:
1- install packages

npm install --save customize-cra react-app-rewired inversify-inject-decorators 
npm install --save-dev @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/preset-typescript babel-plugin-transform-typescript-metadata

(I'm not sure if all of those dev-dependencies are required though)

2- update package.json build and start scripts:

"start": "react-app-rewired start",
"build": "react-app-rewired build",

3- add a file called config-overrides.js at the same level as package.json which includes the following:

const {
    override,
    addBabelPlugins,
} = require("customize-cra");

module.exports = override(
    ...addBabelPlugins(
        "babel-plugin-transform-typescript-metadata", 
    ),
);

you can read about it here

@Glebario
Copy link

I'm on RN 0.59.5, TS 3.4.5, Babel 7.4.4 (core, runtime & plugin-proposal-decorators). I had happily been using @injectable()as a class decorator for ages. When I introduced @inject in the parameter list of a class constructor for the first time, the 'standard' RN project recommendations for Babel & Metro config gave an Unexpected @ token error during the bundling process.

I had to switch to using react-native-typescript-transformer in my metro.config.js to resolve the problem.

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: false,
      },
    }),
    babelTransformerPath: require.resolve('react-native-typescript-transformer')
  },
};

I used inversify and mobx in my project. I also use expo and all possible expo modules. This solution eliminates the problem, but leads to many other problems that will emerge later and all of them will be related to expo. I had to abandon the react-native-typescript-transformer in favor of the default metro transformer. Therefore, if you use expo or are going to use it, I strongly advise you not to use react-native-typescript-transformer. Otherwise, unexpected surprises may await you in the future.
For me, this problem remains relevant.

@MehediEhteshum
Copy link

In my project, I had similar issues caused by babel and inversify @inject directive usage. I used Metro server (not Expo). I believe the solution must be similar for both. I couldn't find a standalone solution. After much trial and error, I solved it.
I hope my solution will save someone a lot of time.

You can find my detailed answer here >> https://stackoverflow.com/a/78696852/13607767

@notaphplover
Copy link
Member

After reading the comments, I understand this is not an inversify issue. I'm closing it for now, feel free to ping me if I'm mistaken.

@behzodfaiziev
Copy link

@notaphplover @ryall only field injection worked in react ts:

  @inject("INetworkManager")
  private networkManager!: INetworkManager;
  // constructor(
  //   @inject("INetworkManager") private networkManager: INetworkManager,
  // ) {}

However, I also have next ts application and constructor injection works as intended. I am from backend/mobile area, and in Spring boot for example field injection is not recommended way. The recommended injection is construction injection, but since, i did not find any solution, I used field injection

@notaphplover
Copy link
Member

Hey @ryall, the thing is, this is probably an issue in the transpiler / bundler you're using. If property decorators are working and parameter decorators not, is there any chance legacy decorators are not properly transpiled? Can you try to do manually a parameter injection via inversify.decorate? Consider docs as reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests