IoC and DI for Vue with InversifyJS inspired by NestJS
- Vue 2.x
- reflect-metadata
- InversifyJS
-
vuex-module-decorators support using lazy injection via
@LazyInject
. - Services are accessible through Vue components.
- Can inject a
VuexStore
if you want type-safety.
$ npm install doguinho reflect-metadata inversify --save
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
}
First, create an Doguinho instance.
import { createDoguinho } from "doguinho";
const doguinho = createDoguinho(options);
You can pass several options during creation such as router
and store
if you want to
define
them in advance, and Doguinho will use them as the basis for defining things, if you do not provide, Doguinho will create its own.
import Vue from "vue";
const vm = new Vue({ doguinho });
Note: you must create Doguinho before building your application.
Modules are closed injection environments and should be used to better organize your application.
To define a new module simply add the decorator @Module
on it.
import { Module } from "doguinho";
@Module()
export default class MyModule {}
In case you need to do something when the module starts, there are initialization handlers
beforeInit
and init
for you to work on.
import { Module, ModuleContext } from "doguinho";
@Module({
onInit(context: ModuleContext): void { /* ... */ }
})
export default class MyModule {}
You can use the current context to perform manual injection, or anything else.
import { Module, ModuleContext } from "doguinho";
@Module({
beforeInit(context: ModuleContext): void {
context.inject(Anything);
}
})
export default class MyModule {}
const HelloWorldKey = "hero";
@Module({
beforeInit(context: ModuleContext): void {
context.injectConst("Hello world", HelloWorldKey);
}
})
export default class MyModule {}
const InitKey = "hero";
@Module({
beforeInit(context: ModuleContext): void {
context.injectConstant(`Module "${context.module.name}" initialized!`, InitKey);
},
init(context: ModuleContext): void {
console.log(context.get(InitKey));
// prints: Module "auth" initialized
}
})
export default class AuthModule {}
Providers passed through modules are automatically injected in the context of that module.
Only classes annotated with
@Injectable
can be providers.
import { Module, Injectable } from "doguinho";
@Injectable()
export class SomeRandomService {}
@Module({
providers: [SomeRandomService]
})
export default class MyModule {}
A recommended project structure using modules is the feature module
which consists of a
directory (with its own module) for each feature. Something like this:
Source directory (/src)
├─ app/
│ │ ├─ module-A/
│ │ │ ├─ A.module.ts
│ │ ├─ module-B/
│ │ │ ├─ B.module.ts
│ │ app.module
│ │ App.vue
│ ├─ main.ts
Inject any type of instantiable class in the context of the module.
Anything injected becomes singleton in the module context.
When necessary, InversifyJS will instantiate the object and keep it available for other services.
@Injectable
export class DogService {}
doguinho.inject(DogService);
Inject other services available in the module into your service constructor.
@Injectable
export class CatsService {
constructor(@Inject() private readonly dogsService: DogsService) {}
}
doguinho.inject(DogsService, CatsService);
It is often not possible for InversifyJS to instantiate the object being injected, for example: Vue components or Vuex stores.
Considering that there is no specific order of startup and that we do not know what will or will not be available or when, we created the lazy injection.
Make a note of a property so that it becomes a getter which, when requested, will get the result late.
export class Somewhere {
@Inject() private readonly dogsService!: DogsService
}
Note: providers are searched for in their own module and then in the global context if they are not found.
@Module
export default class DogsStore extends VuexModule {
@Inject() private readonly dogsService!: DogsService;
@Action
public async createDog(name: string): Promise<Dog> {
// do something with DogsService
}
}
You can inject the store so that it will be available anywhere in the scope of that module.
doguinho.inject(DogStore);
@Component
export default class SomeComponent extends Vue {
@Inject() private readonly dogsStore!: DogsStore;
/* ... do something wih DogsStore */
}