You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
"TypeScript's constructor assignments" is not working #1555
Environment name and version (e.g. Chrome 39, node.js 5.4): Node.js 18.19.0
Operating System and version (desktop or mobile): macOS 14.2.1
Link to your project: N/A
Stack trace
Error: Missing required @inject or @multiInject annotation in: argument 0 in class Ninja.
at getConstructorArgsAsTarget (/xxx/node_modules/inversify/lib/planning/reflection_utils.js:78:19)
at getConstructorArgsAsTargets (/xxx/node_modules/inversify/lib/planning/reflection_utils.js:90:22)
at getTargets (/xxx/node_modules/inversify/lib/planning/reflection_utils.js:56:30)
at getDependencies (/xxx/node_modules/inversify/lib/planning/reflection_utils.js:41:12)
at /xxx/node_modules/inversify/lib/planning/planner.js:124:71
at Array.forEach (<anonymous>)
at _createSubRequests (/xxx/node_modules/inversify/lib/planning/planner.js:112:20)
at plan (/xxx/node_modules/inversify/lib/planning/planner.js:154:9)
at /xxx/node_modules/inversify/lib/container/container.js:626:46
at Container._get (/xxx/node_modules/inversify/lib/container/container.js:596:38)
Please try to recreate it in codesandbox or in any other platform in which we can see the issue. We cannot identify nor solve the issue without recreating it
I tested with the environment provided, library version, os and nodejs version, and everything works just fine. Would be nice to have your tsconfig file just to make sure you have all the necessary elements to run your container correctly as well as the Typescript lib version, if exists on your package.json
Here are the steps:
MINIMUM TSCONFIG (you can use any supported target, I am just using the latest one)
When I created a brand new project with @rsaz's code and compiled it with tsc, it worked. But I'm having a hard time making it work on a remix app.
Since it just worked fine on its own, I'm guessing it's an issue with the remix compiler or just how I set it up. Here's the minimium repro. What am I missing?
Now related to the issue referred in this ticket: Missing required @inject or @multiInject annotation in: argument 0 in class Ninja.
The reason why you're getting the issue above is just the nature of Typescript works. As we all know, Typescript doesn't have a good reflection system, so Inversify relies heavily on reflect-metadata to capture information about your classes.
When your declaration of classes are straightforward, injected outside of any extra context (in this case handleRequest), this type of declaration `constructor(private _dagger: Dagger), works out-of-the-box as reflect-metadata can do it's job. However, when are there other contexts or component layers, in your case a default react component, that has its own life cycle, can interfere in the gathering of metadata information.
Inversify resolves this issue forcing the use of the inject decorator to be able to read the metadata of the classes
1 - using the @inject() decorator, so all data is captured in the decorator level constructor(@inject(Dagger) private _dagger: Dagger) {}
FINAL CODE
import"reflect-metadata";import{Container,inject,injectable}from"inversify";/** * By default, Remix will handle generating the HTTP Response for you. * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ * For more information, see https://remix.run/file-conventions/entry.server */import{PassThrough}from"node:stream";importtype{AppLoadContext,EntryContext}from"@remix-run/node";import{createReadableStreamFromReadable}from"@remix-run/node";import{RemixServer}from"@remix-run/react";import{isbot}from"isbot";import{renderToPipeableStream}from"react-dom/server";constABORT_DELAY=5_000;exportdefaultfunctionhandleRequest(request: Request,responseStatusCode: number,responseHeaders: Headers,remixContext: EntryContext,// This is ignored so we can keep it in the template for visibility. Feel// free to delete this parameter in your app if you're not using it!// eslint-disable-next-line @typescript-eslint/no-unused-varsloadContext: AppLoadContext){
@injectable()classDagger{publicthrow(){console.log("throw dagger");}}
@injectable()classNinja{publicconstructor(@inject(Dagger)private_dagger: Dagger){}publicthrowDagger(){this._dagger.throw();}}constcontainer=newContainer();container.bind<Ninja>(Ninja).toSelf();container.bind<Dagger>(Dagger).toSelf();container.get(Ninja).throwDagger();returnisbot(request.headers.get("user-agent")||"")
? handleBotRequest(request,responseStatusCode,responseHeaders,remixContext)
: handleBrowserRequest(request,responseStatusCode,responseHeaders,remixContext);}functionhandleBotRequest(request: Request,responseStatusCode: number,responseHeaders: Headers,remixContext: EntryContext){returnnewPromise((resolve,reject)=>{letshellRendered=false;const{ pipe, abort }=renderToPipeableStream(<RemixServercontext={remixContext}url={request.url}abortDelay={ABORT_DELAY}/>,{onAllReady(){shellRendered=true;constbody=newPassThrough();conststream=createReadableStreamFromReadable(body);responseHeaders.set("Content-Type","text/html");resolve(newResponse(stream,{headers: responseHeaders,status: responseStatusCode,}));pipe(body);},onShellError(error: unknown){reject(error);},onError(error: unknown){responseStatusCode=500;// Log streaming rendering errors from inside the shell. Don't log// errors encountered during initial shell rendering since they'll// reject and get logged in handleDocumentRequest.if(shellRendered){console.error(error);}},});setTimeout(abort,ABORT_DELAY);});}functionhandleBrowserRequest(request: Request,responseStatusCode: number,responseHeaders: Headers,remixContext: EntryContext){returnnewPromise((resolve,reject)=>{letshellRendered=false;const{ pipe, abort }=renderToPipeableStream(<RemixServercontext={remixContext}url={request.url}abortDelay={ABORT_DELAY}/>,{onShellReady(){shellRendered=true;constbody=newPassThrough();conststream=createReadableStreamFromReadable(body);responseHeaders.set("Content-Type","text/html");resolve(newResponse(stream,{headers: responseHeaders,status: responseStatusCode,}));pipe(body);},onShellError(error: unknown){reject(error);},onError(error: unknown){responseStatusCode=500;// Log streaming rendering errors from inside the shell. Don't log// errors encountered during initial shell rendering since they'll// reject and get logged in handleDocumentRequest.if(shellRendered){console.error(error);}},});setTimeout(abort,ABORT_DELAY);});}
Activity
mathysth commentedon Mar 17, 2024
Hey, I have the same problem did you found an anwser ?
mizozobu commentedon Mar 20, 2024
Nope. I'm using
@injectinstead.cloudhx commentedon Sep 17, 2024
Have the same issue, filed another issue
notaphplover commentedon Oct 21, 2024
Hey @mizozobu, @cloudhx , I just recreated it with a test and worked like a charm:
Please try to recreate it in codesandbox or in any other platform in which we can see the issue. We cannot identify nor solve the issue without recreating it
rsaz commentedon Oct 23, 2024
Hi @mizozobu and @cloudhx
I tested with the environment provided, library version, os and nodejs version, and everything works just fine. Would be nice to have your
tsconfigfile just to make sure you have all the necessary elements to run your container correctly as well as the Typescript lib version, if exists on yourpackage.jsonHere are the steps:
MINIMUM TSCONFIG (you can use any supported target, I am just using the latest one)
{ "compilerOptions": { "target": "ES2023", "experimentalDecorators": true, "emitDecoratorMetadata": true, "module": "commonjs", "forceConsistentCasingInFileNames": true, "strict": true, "skipDefaultLibCheck": true, "skipLibCheck": true, "strictPropertyInitialization": false } }MUST HAVE DEPENDENCY
Obs: Reflect metadata has to be the first thing to be initialized on top of your project
CODE
Should work just fine. Please, let us know if the issue has been resolved based on the configuration I gave you, so that we can close this topic.
OUTPUT
mizozobu commentedon Oct 23, 2024
When I created a brand new project with @rsaz's code and compiled it with
tsc, it worked. But I'm having a hard time making it work on a remix app.Since it just worked fine on its own, I'm guessing it's an issue with the remix compiler or just how I set it up. Here's the minimium repro. What am I missing?
https://github.com/mizozobu/inversify-remix-repro
rsaz commentedon Oct 23, 2024
Hi @mizozobu
The issue here is architectural. Let me walk you through the situation:
1 - In your code below, you're trying to inject a class, in this case
Daggerbefore its definition, like belowChange the order to:
Now related to the issue referred in this ticket:
Missing required @inject or @multiInject annotation in: argument 0 in class Ninja.The reason why you're getting the issue above is just the nature of Typescript works. As we all know, Typescript doesn't have a good reflection system, so Inversify relies heavily on
reflect-metadatato capture information about your classes.When your declaration of classes are straightforward, injected outside of any extra context (in this case
handleRequest), this type of declaration `constructor(private _dagger: Dagger), works out-of-the-box as reflect-metadata can do it's job. However, when are there other contexts or component layers, in your case a default react component, that has its own life cycle, can interfere in the gathering of metadata information.Inversify resolves this issue forcing the use of the
injectdecorator to be able to read the metadata of the classes1 - using the
@inject()decorator, so all data is captured in the decorator levelconstructor(@inject(Dagger) private _dagger: Dagger) {}FINAL CODE
OUTPUT
