reactjstypescriptdependency-injectiontsyringe

How to make tsyringe (dep injection lib) resolve classes that have dependencies?


I am clearly misunderstanding how TSyringe is supposed to resolve classes with dependencies.

I created a minimal repro. In my index.tsx, I do as they indicate in the docs and import reflect-metadata. This example works if I inject a singleton class with no dependencies:

// index.tsx
import "reflect-metadata";
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />,  document.getElementById('root'));

A single singleton class:

// _A.ts
import {inject, singleton} from 'tsyringe';

@singleton()
export class A {    
    get_msg() {
        return "Worked!";
    }
}

And the component that uses it:

// App.tsx
import React from 'react';
import './App.css';
import {container} from "tsyringe";
import {A} from "./_A";

interface Props {
  a?: A
}

function App({a = container.resolve(A)}: Props) {
  return (
    <div className="App">
          {a.get_msg()}
    </div>
  );
}

export default App;

When I run the app, the Worked! text is printed as expected.

However, if I create a second singleton called B:

// _B.ts
import {singleton} from 'tsyringe';

@singleton()
export class B {
    get_msg() {
        return "Worked!";
    }
}

And then inject B into A to get the message:

// _A.ts
import {inject, singleton} from 'tsyringe';
import {B} from "./_B";

@singleton()
export class _A {
    constructor(private b: B) {
    }

    get_msg() {
        return this.b.get_msg();
    }
}

Then it fails with Uncaught Error: TypeInfo not known for "A"

I have:

"experimentalDecorators": true,
"emitDecoratorMetadata": true,

on my tsconfig.ts, as they indicate in the README.

Shouldn't Syringe resolve B automatically, inject B into A and then inject A into my app component so I can print the message?

What am I missing?


Solution

  • OK so I figured this out.

    To keep the typescript metadata and allow reflect-metadata to do its thing, we need to add babel-plugin-transform-typescript-metadata to the project.

    However, in order to customize create-react-app, you need the craco library. There are several out there, but craco is the only one I could that supports CRA 4.x. You cannot be on the latest (CRA 5.0) as none of these libs support it yet.

    So:

    1 - Install Craco and set it up.

    2 - Install babel-plugin-transform-typescript-metadata as a dev dependency

    3 - Add a craco.config.js file to your project to load this plugin:

    module.exports = function ({ env: _env }) {
        return {
            babel: {
                plugins: [
                    "babel-plugin-transform-typescript-metadata"
                ]
            },
        };
    };
    

    4 - Make sure to update your package.json to launch with craco so that the config override takes place:

      "scripts": {
        "start": "craco start",
        "build": "craco build",
        "test": "craco test",
        "eject": "craco eject"
      },
    

    That's it, it will work now. Hope it helps someone out there.