Dependency injection for TypeScript, without the boilerplate. inversify-props wraps InversifyJS so you can register a class once and inject it as a property (or constructor parameter) with a single @inject() — no manual ids, no fluent binding ceremony. Framework-agnostic: works with Vue, LitElement, vanilla TS, or anything else. Built on inversify 8.
Using React? Try the companion package inversify-hooks, a thin useInject layer on top of this one.
- Why
- Installation
- TypeScript configuration
- Quick start
- Registering dependencies
- Injecting dependencies
- Custom ids
- Your own container
- Testing with mocks
- API reference
- Runnable examples
- Use it as an agent skill
- Troubleshooting
- Credits
Plain InversifyJS asks you to manage service identifiers and write fluent bindings for every dependency. inversify-props does that for you: it derives ids from class names automatically and lets you inject by property. You probably don't need it if you want the full power of inversify (custom scopes everywhere, factories, multi-injection) — it's intentionally a thin convenience layer for the common "inject my services" case.
npm install inversify-propsBuilt on inversify 8, which is pulled in for you — no separate reflect-metadata install needed. Type definitions and both ESM and CommonJS builds ship with the package.
Note: inversify 8 is ESM-first. Bundlers (Vite, webpack, etc.) need nothing special; consuming it via
require()from plain CommonJS Node needs Node 20.19+ or 22+.
inversify-props uses legacy decorators, so enable experimentalDecorators and keep useDefineForClassFields off:
⚠️ IfuseDefineForClassFieldsistrue, an injected property's class field shadows the injected getter and reads backundefined. It defaults tofalsewhentargetis belowES2022.
import { cid, container, inject } from 'inversify-props';
// 1. Define a class + interface
interface IService1 {
method1(): string;
}
class Service1 implements IService1 {
method1() {
return 'method 1';
}
}
// 2. Register it (usually in app.container.ts), before anything resolves
container.addSingleton<IService1>(Service1);
// 3. Inject it anywhere
class MyComponent {
@inject() service1!: IService1;
}
// ...or resolve it directly
const service1 = container.get<IService1>(cid.IService1);
⚠️ If you use a minifier/obfuscator, configure it to preserve class and function names — see Troubleshooting.
Register on the shared container before resolving. Three lifetimes are available:
container.addSingleton<IService1>(Service1); // one shared instance (most common)
container.addTransient<ILogger>(Logger); // a new instance on every resolve
container.addRequest<IUnitOfWork>(UnitOfWork); // one instance per request scopeThe generic (<IService1>) is just the compile-time type. The runtime id is derived from the class name and cached under both Service1 and IService1, which is why cid.IService1 works.
Inject as a property — the id is inferred from the property name (camelCase, PascalCase and leading _ are all handled):
export class MyOtherService {
@inject() private service1: IService1;
@inject() private _service1: IService1;
@inject() private Service1: IService1;
}Constructor injection infers the id from the parameter name:
export class MyOtherService {
constructor(@inject() private exampleService: IExampleService) {}
}Don't want auto-generated ids? Pass your own:
container.addSingleton<IService1>(Service1, 'MyService1');
export class MyComponent {
@inject('MyService1') service1!: IService1;
}The library ships a ready-to-use container, but you can create and install your own:
import { Container, setContainer } from 'inversify-props';
setContainer(new Container());The recommended pattern, before each test: reset the container, re-register dependencies, then mock what the test needs.
import { cid, mockSingleton, resetContainer } from 'inversify-props';
beforeEach(() => {
resetContainer();
containerBuilder(); // your registrations
mockSingleton<IHttpService>(cid.IHttpService, HttpServiceMock);
});mockTransient and mockRequest have the same signature. resetContainer() unbinds everything.
| Export | Description |
|---|---|
container |
The shared container instance (auto-created). |
container.addSingleton<T>(Class, id?) |
Register a single shared instance. |
container.addTransient<T>(Class, id?) |
Register a new instance per resolve. |
container.addRequest<T>(Class, id?) |
Register one instance per request scope. |
container.get<T>(id) |
Resolve a dependency by id. |
cid |
Cache of generated ids, e.g. cid.IService1. |
inject / Inject |
Property/parameter decorator for injecting dependencies. |
injectable |
Class decorator marking a class as injectable. |
mockSingleton / mockTransient / mockRequest |
Replace a registered id with another implementation (testing). |
resetContainer() |
Unbind everything from the container. |
getContainer() / setContainer(opts) |
Access or replace the underlying InversifyJS container. |
Container |
The container class, if you want your own instance. |
Two minimal demos live in examples/, each showing a service that injects another service via @inject():
examples/vue— Vue 3 + Viteexamples/lit— Lit 3 + Vite
# from the repo root — build the library first
npm install && npm run build
cd examples/vue # or examples/lit
npm install
npm run devThis repo ships an Agent Skill so AI coding agents (Claude Code, Cursor, etc.) know how to wire up DI with this library. Install it with npx skills:
npx skills add CKGrafico/inversify-props| Symptom | Cause & fix |
|---|---|
Injected property is undefined |
useDefineForClassFields is true. Set it to false (or keep target below ES2022). |
| Works in dev, breaks in production | The minifier mangled class names, so cid.IXxx is undefined. Enable keepNames (esbuild/Vite) or keep_classnames + keep_fnames (Terser/Uglify). Vue CLI uses Terser under the hood — set it via configureWebpack → optimization.minimizer. |
No matching bindings found |
The service wasn't registered, or registration ran after it was resolved. Register first. |
| Decorators throw at runtime | experimentalDecorators is off in your tsconfig.json. |
Built on InversifyJS. Licensed under MIT.

{ "compilerOptions": { "target": "es2020", "lib": ["es2020", "dom"], "moduleResolution": "bundler", "experimentalDecorators": true, "useDefineForClassFields": false } }