Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve injected instances in Vue.js using TypeScript

According to the official document, it seems that we can use dependency injection feature as long as we stay on object-based structure.

Here's my question. I'm using TypeScript to achieve this goal (class-based). I'm going to use Inversify as an IoC container. My initial idea was something like:

DependencyConfig.ts:

import { Container } from "inversify";
import "reflect-metadata";
import Warrior from "./interfaces/Warrior";
import { Ninja } from "./models/Warrior";

let container = new Container();

container.bind<Warrior>(Symbol("Warrior")).to(Ninja);

export default container;

App.ts:

import container from "./DependencyConfig";

@Component({
  name: "App",
  provide: container
})
export default class App extends Vue {
}

When I checked in my browser's dev console, I was able to see the container has been set to the _provided field. Here's Hello.ts, the child component of App.ts:

Hello.ts:

@Component({
  name: "Hello",
  inject: [ "container" ]
})
export default class Hello extends Vue {
  created (): void {
    console.log(this);
  }
}

As App.ts could access to Hello.ts through vue-router, it didn't register Hello.ts as a child component. I was expecting the injected container should appear on _injected or something similar. However, I couldn't find it. I changed the inject property value from "container" to { "container": Symbol("Container") } but I couldn't still find it.

Service Locator:

It works fine to use a service locator instead of the provide/inject pair:

// App.ts
@Component({
  name: "App"
})
export default class App extends Vue {
}

// Hello.ts
import container from "./DependencyConfig";

@Component({
  name: "Hello"
})
export default class Hello extends Vue {
  created (): void {
    var ninja = container.get<Ninja>(Symbol("Warrior"));
    console.log(ninja.name);
  }
}

However, I want to avoid using the service locator pattern here. Did I miss something while using the provide/inject pair for dependency injection?

like image 784
justinyoo Avatar asked Mar 20 '17 00:03

justinyoo


1 Answers

I found a solution. If we use vue-class-component and vue-property-decorator, we can achieve this goal – using the provide/inject pair. Here's my code snippet:

// App.vue
<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";
import SERVICE_IDENTIFIER from "./models/Identifiers";
import container from "./configs/DependencyConfigs";

@Component({
  name: "App",
  // Provides IoC container at the top level of VueComponent
  provide: {
    [SERVICE_IDENTIFIER.CONTAINER]: container
  }
})
export default class App extends Vue {
}
</script>

At the top-most Vue component, App.vue, we provide the container instance so that its all child component can consume it. Here's one of its child component, Ninja.vue:

// Ninja.vue
<script lang="ts">
import Vue from "vue";

// Imports both Component and Inject decorators from vue-property-decorator,
// instead of vue-class-component
import { Component, Inject } from "vue-property-decorator";

import { Container } from "inversify";

import SERVICE_IDENTIFIER from "../models/Identifiers";
import { Ninja as _Ninja } from "../models/Warrior";

@Component({
  name: "Ninja"
})
export default class Ninja extends Vue {
  public warrior: string;
  public weapon: string;

  // IoC container provided from App.ts is injected here
  @Inject(SERVICE_IDENTIFIER.CONTAINER)
  private _container: Container;

  private _ninja: _Ninja;

  created (): void {
    this._ninja = this._container.get<_Ninja>(SERVICE_IDENTIFIER.WARRIOR);
    this.warrior = this._ninja.name;
    this.weapon = this._ninja.weapon.name;
  }
}
</script>

The child component uses the @Inject(Symbol) decorator to resolve injected container instance from App.vue.

I wrote a blog post about this in both English and Korean about this.

HTH

like image 167
justinyoo Avatar answered Nov 10 '22 10:11

justinyoo