microsoft / tsyringe

Lightweight dependency injection container for JavaScript/TypeScript
MIT License
5.16k stars 173 forks source link

Unable to find dependencies when injecting @injectable classes that have the @registry annotation from another file. #139

Closed spik3r closed 3 years ago

spik3r commented 3 years ago

Describe the bug

Unable to find dependencies when injecting @injectable classes that have the @registry annotation from another file.

To Reproduce

The following sample code works and can be seen running here: https://stackblitz.com/edit/typescript-ys8qjv?file=index.ts The issue problem I'm facing is the same code doesn't work when subclasses eg Abc, Foo and Baz are defined in different files

import "./style.css";
import "reflect-metadata";
import { injectable, registry, container } from "tsyringe";

class RestController {}

//Imagine all these classes are in their own files
@injectable()
@registry([
  {
    token: "RestController",
    useToken: RestController
  }
])
class Abc extends RestController {}

@injectable()
@registry([
  {
    token: "RestController",
    useToken: RestController
  }
])
class Foo extends RestController {}

@injectable()
@registry([
  {
    token: "RestController",
    useToken: RestController
  }
])
class Baz extends RestController {}

const controllers: RestController[] = container.resolveAll<RestController>(
  "RestController"
);

const appDiv: HTMLElement = document.getElementById("app");
appDiv.innerHTML = `<h1>Injected Dependencies ${controllers.length}</h1>`;

Expected behavior

I expect to be able to inject classes defined in different files without needing to explicitly add them to the registry in the class using them. It would also be awesome if we didn't need the registry annotation and could maybe inject it based on the type only or add the token to the @injectable annotation similar to how SpringBoot handles the bean or component annotations. Version: 4.4.0

MeltingMosaic commented 3 years ago

Can you share your example where it doesn't work? I'm wondering if the registry() decorators are running or not.

spik3r commented 3 years ago

Hi @MeltingMosaic I updated the example: https://stackblitz.com/edit/typescript-wjjeyq?devtoolsheight=33&file=index.ts

Basically the same code except it's moved to seperate files but doing that results in this error being thrown: Attempted to resolve unregistered dependency token: "RestController"

MeltingMosaic commented 3 years ago

Got it, so what I think is happening here is that in index.ts it doesn't know of the existence of Abc Foo and Baz. The registry() decorator runs (and the type gets registered) when a module is imported, and there are no imports for those types. This is a little different than coming from Java or .NET where the dependency injector can introspect the entire package and find injectables.

Here's a quick fix (not sure this will meet your needs)

import "./style.css";
import "reflect-metadata";
import { injectable, registry, container } from "tsyringe";
import RestController from "./RestController";
import "./Abc";
import "./Baz";
import "./Foo";

const controllers: RestController[] = container.resolveAll<RestController>(
  "RestController"
);

const appDiv: HTMLElement = document.getElementById("app");
appDiv.innerHTML = `<h1>Injected Dependencies ${controllers.length}</h1>`;
spik3r commented 3 years ago

Thank you! That makes sense I think your suggestion should work for now.