Minimalistic IoC/DI container for TypeScript without dependencies which does exactly what it should and not more. Proof of concept that IoC/DI container implementation can be that simple.
Out-of-the-box it can only provide you with singleton or everytime-new instances of any class. In case you need something more complex you can register a custom resolver.
Doesn't support primitive values. Because it shouldn't.
Doesn't support TypeScript interface
and type
. Actually there are no libraries supporting them since types and interfaces are ephemeral build time entities and they just don't exist in runtime.
Integrates smoothly to Vue.js 2/3 projects with vue-class-component or composition API.
npm i mini-ioc
If you want to resolve classes automatically, you have 2 options.
- Use decorators with type metadata
- Install
reflect-metadata
to be able to pass type information about constructor arguments and properties to runtime - Add
import 'reflect-metadata'
to the entry point of your application to make TypeScript provide decorated entities with reflection metadata - Configure TypeScript to support decorators and runtime type metadata with flags
compilerOptions.experimentalDecorators = true
andcompilerOptions.emitDecoratorMetadata = true
in yourtsconfig.json
- Install
- Use
inject
function. It is available within a class constructor and can be used to resolve dependencies without metadata provided by decorators.
Then you can use an IOC container like this:
import { Resolvable } from "mini-ioc";
// Any class you are going to resolve with container should be decorated with Resolvable...
@Resolvable
class SomeClass {}
@Resolvable
class SomeOtherClass {
public constructor(
// Constructor arguments will be resolved by a container automatically based on metadata
public someInstance: SomeClass
);
}
// ...or use `inject` instead
import { inject } from "mini-ioc";
class SomeClass {}
@Resolvable
class SomeOtherClass {
public constructor(
// Constructor arguments will be resolved by the inject function on constructor call
public someInstance = inject(SomeClass)
);
}
import Container from "mini-ioc";
// Container instance
const container = new Container();
// Resolve class as a single instance every time (like singleton)
const single = container.get(SomeOtherClass);
console.log(single === container.get(SomeOtherClass)); // true
// Resolve a fresh class instance
const newInstance = container.create(SomeOtherClass);
console.log(single === newInstance); // false
// Constructor arguments are resolved automatically as single instances with container.get
console.log(single.someInstance === newInstance.someInstance); // true
You can provide any subclass to create a class instance from.
import Container, { Resolvable } from "mini-ioc";
abstract class BaseClass {}
@Resolvable
class SubClass extends BaseClass {}
const container = new Container();
// Bind abstract class to its implementation
container.bind(BaseClass, SubClass);
console.log(container.get(BaseClass) instanceof SubClass); // true
You can completely override default behavior for a class instance creation.
import Container from "mini-ioc";
abstract class BaseClass {}
class SubClass extends BaseClass {
protected field!: string;
public static function makeInstance() {
const instance = new SubClass();
instance.field = "Hello there";
return instance;
}
}
const container = new Container();
// Register custom function as a resolver for BaseClass
// Mind that you don't have to use Resolvable decorator nor `inject` for custom resolvers.
// Helpful for third-party libraries.
container.registerResolver(BaseClass, (classCtor, container) => SubClass.makeInstance());
// Make instance using default resolving behavior and add some initialization logic.
container.registerResolver(BaseClass, (classCtor, container) => {
const instance = new SubClass(...container.getResolvedArguments(SubClass));
instance.initSomethingImportant(42);
return instance;
});
console.log(container.get(BaseClass) instanceof SubClass); // true
npm i mini-ioc mini-ioc-vue
To make things work in Vue.js components you should register mini-ioc container in app root provide
:
import Container from "mini-ioc";
import { injectKey } from "mini-ioc-vue";
const container = new Container();
// configure container bindings here
// for Vue 2
import Vue from "vue";
const app = new Vue({
provide: {
[injectKey]: container,
},
});
// for Vue 3
import { createApp } from "vue";
const app = createApp();
app.provide(injectKey, container);
Resolving will work for both Vue 2 and 3, but typing will be available only for Vue 3 with defineComponent
.
import { defineComponent } from "vue";
import { injectMixin, computedResolver } from "mini-ioc-vue";
import SomeClass from "./anywhere";
defineComponent({
mixins: [injectMixin],
computed: {
// resolve as a singleton (container.get)
someInstance: computedResolver(SomeClass),
// resolve as an everytime-new instance (container.create)
freshSomeInstance: computedResolver(SomeClass, true),
},
});
First you should provide additional type information for inject
somewhere in .d.ts
file within your project (src/mini-ioc.d.ts
for example).
Library itself could provide that too but in this case it will only support one of Vue 2 + @vue/composition-api
or Vue 3. So some manual actions here are the compromise to support both. Sorry for the inconvenience :)
declare module 'mini-ioc-vue' {
import type Container from 'mini-ioc';
import type { InjectionKey } from 'vue'; // for Vue 3
// import type { InjectionKey } from '@vue/composition-api'; // for Vue 2
export const injectKey: InjectionKey<Container>;
}
import { defineComponent, inject } from "vue";
import { injectKey } from "mini-ioc-vue";
import SomeClass from "./anywhere";
defineComponent({
setup() {
const container = inject(injectKey);
if (!container) throw new Error("No container - no component, pal. Come back when have one.");
const someInstance = container.get(SomeClass);
const freshSomeInstance = container.create(SomeClass);
},
});
npm i mini-ioc mini-ioc-vue mini-ioc-vue-class
Decorators are using computedResolver
under-the-hood so the result is the same as using options API.
import { Inject, InjectNew } from "mini-ioc-vue-class";
import SomeClass from "./anywhere";
class MyComponent {
// resolve as a singleton (container.get)
@Inject
someInstance!: SomeClass;
// resolve as an everytime-new instance (container.create)
@InjectNew
freshSomeInstance!: SomeClass;
}