Code Monkey home page Code Monkey logo

vue-ioc's Introduction

vue-ioc

IoC and DI for Vue powered by InversifyJS and inspired by Angular @Module syntactic sugar.

Required Opinionated Stack

Features

  1. Hierarchical IoC Container defined in @Module by providers (using InversifyJS under the hood). The hierarchy is bound to Vue components tree.
  2. Autostart - instantiating top level services when container has been started (for background tasks similar to @Effects from ngrx).
  3. @InjectReactive() - makes injected dependency 'deeply reactive' in Vue template.
  4. Instance Handlers - @PostConstruct and @BeforeDestroy - decorators for methods called when instance is created or destroyed by container.
  5. Custom Instance Handlers ie. @OnEvent('submitForm')
  6. providedIn 'root' or 'self' - select where will be bound @Injectable (friendly for tree shakeable singletons and lazy loaded @Modules)

Planned features (not ready yet)

  1. State Injectors for Vuex and MobX.
  2. vue-cli integration

Caveats / Limitations

  • @Module can be bound only to Vue component.
  • You can't use @Inject() for Vue component constructor arguments (because you can't define own constructor using vue-class-component). Only Vue component class fields are supported.

Installation

vue-ioc uses following peerDependencies:

  • inversify
  • reflect-metadata
  • vue-class-component
  • vue
# NPM
npm install @vue-ioc/core inversify reflect-metadata vue-class-component vue --save

# Yarn
yarn add @vue-ioc/core inversify reflect-metadata vue-class-component vue

You must explicitly install vue-ioc via Vue.use() in your app main entrypoint:

// main.ts
import Vue from 'vue'
import {VueIocPlugin} from '@vue-ioc/core' 

Vue.use(VueIocPlugin)

Quick Start

Edit vue-ioc basic example

Create simple injectable service HttpService:

// ./services/HttpService.ts
import {Injectable} from '@vue-ioc/core';

@Injectable()
export class HttpService {
    public get(url: string): Promise<any> {
        return fetch(url).then(rs => rs.json());
    }
}

Add root container using @Module decorator at your top level App component and setup providers:

// App.vue
<template>
    <div>
        <HelloWorld/>
    </div>
</template>
<script lang="ts">
    import Vue from 'vue'
    import Component from 'vue-class-component'
    import {Module} from '@vue-ioc/core';
    import {HttpService} from './services/HttpService';
    import HelloWorld from './components/HelloWorld.vue';
    
    @Module({
        providers: [
            HttpService
        ]
    })
    @Component({
      components: { HelloWorld }
    })
    export default class App extends Vue {
    }
</script>

Inject HttpService to <HelloWorld> component:

// ./components/HelloWorld.vue
<template>
    <div>
        <h2 v-if="user">Hello {{user.firstName}} {{user.lastName}}!</h2>
    </div>
</template>
<script lang="ts">
    import Vue from 'vue'
    import Component from 'vue-class-component'
    import {Inject} from '@vue-ioc/core';
    import {HttpService} from '../services/HttpService';

    @Component()
    export default class HelloWorld extends Vue {

        @Inject()
        public httpService: HttpService;
        
        public user = null;

        public async created() {
            this.user =  await this.httpService.get('./hello.json');
        }
    }
</script>

Providers

@Module({
    providers: [
        // useClass
        { provide: HttpService, useClass: HttpService },
        
        // useClass shortcut 
        HttpService,
        
        // useValue
        { provide: HttpService, useValue: httpService },
        
        // useFactory
        { provide: HttpService, useFactory: (injector) => /* ...injector.get(FooService)... */ }
    ] 
})

providedIn

Default value: 'self' Available values: 'root', 'self'

@Injectable( { providedIn: 'root' } ) will bind service in root (App) container always as singleton.

You may also override this setting at @Module providers level in both directions:

@Module({
    providers: [
        { provide: HttpService, useClass: HttpService, providedIn: 'root' },   // overrides @Injectable()
        { provide: OtherService, useClass: OtherService, providedIn: 'self' }, // overrides @Injectable( {providedIn: 'root'} )
    ] 
})

Custom Instance Handlers

@PostConstruct() and @BeforeDestroy() are two built in instance listeners. You may create custom instance handlers like @OnEvent('submitForm') by creating a decorator using createInstanceHandlerDecorator

  1. Prepare the most basic EventBus implementation:
// bus/EventBus.ts
import Vue from 'vue';
import {Injectable} from '@vue-ioc/core';

@Injectable()
export class EventBus {

    private bus: Vue = new Vue();

    dispatch(name: string, data: any) {
        this.bus.$emit(name, data);
    }

    addListener(name: string, listener: (data: any) => void) {
        this.bus.$on(name, listener)
    }

    removeListener(name: string, listener: (data: any) => void) {
        this.bus.$off(name, listener)
    }
}
  1. Create @OnEvent(name:string) decorator
// bus/OnEvent.ts
import {createInstanceHandlerDecorator} from '@vue-ioc/core';
import {EventBus} from './EventBus';

export function OnEvent(name: string) {
    return createInstanceHandlerDecorator(({injector, instance, method}) => {
        // attach handler - a place where listeners should be attached
        const bus: EventBus = injector.get(EventBus); // you have access to injector where all services can be retrieved
        const boundMethod = instance[method].bind(instance); // bound method to `this` of instance
        bus.addListener(name, boundMethod);
        return () => { 
            // detach handler - a place where all listeners should be detached
            bus.removeListener(name, boundMethod);
        };
    });
}
  1. Dispatch event from view:
// view/Form.vue
<template>
    <div>
        <button @click="submitForm">Submit</button>
    </div>
</template>
<script lang="ts">
    import Vue from 'vue'
    import Component from 'vue-class-component'
    import {Inject} from '@vue-ioc/core';
    import {EventBus} from '../bus/EventBus';

    @Component()
    export default class SomeForm extends Vue {

        @Inject()
        public bus!: EventBus;

        public submitForm() {
            this.bus.dispatch('submitForm', { firstName: 'John', lastName: 'Doe'})
        }
    }
</script>
  1. Handle event in external action:
// actions/SubmitForm.ts
import {OnEvent} from '../bus/OnEvent'
import {Injectable} from '@vue-ioc/core';

@Injectable()
export class SubmitForm {
  
  @OnEvent('submitForm')
  perform (data) {
     // do something with data
  }
}

Inversify Container Options

vue-ioc uses following default options for creating Inversify containers:

{
    autoBindInjectable: false,
    defaultScope: 'Singleton',
    skipBaseClassChecks: true,
}

To override or add other options, please use containerOptions of plugin options:

Vue.use(VueIocPlugin, {
  containerOptions: {
    // options of Inversify container:
    // https://github.com/inversify/InversifyJS/blob/master/wiki/container_api.md#container-options
  }
})

vue-ioc's People

Contributors

dependabot[bot] avatar gejgalis avatar purplebooth avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

torns learnapp-co

vue-ioc's Issues

how add plugin?

hello, thank you for the source code you made. is it possible for me to add a plugin on httpservice.ts that I declared in the constructor like Angular?

example:
in httpservice.ts

import { Example } from "blabla";

export class HttpService{
constructor(
public example:Example
}{}

im trying like this and im getting error?
any idea?
thankyou sir

SSR window is not defined

I have app with SSR. After i install vue-ioc via Vue.use() in app i get error

ReferenceError: window is not defined
    at Object.factory (/home/abratko/webprojects/trg/whitelabel/whitelabel-vue/vsf-whitelabel-theme/node_modules/@vue-ioc/core/dist/webpack:/VueIoc/webpack/universalModuleDefinition:9:20)
    at Module._compile (internal/modules/cjs/loader.js:1156:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1176:10)
    at Module.load (internal/modules/cjs/loader.js:1000:32)
    at Function.Module._load (internal/modules/cjs/loader.js:899:14)
    at Module.require (internal/modules/cjs/loader.js:1042:19)
    at require (internal/modules/cjs/helpers.js:77:18

Can you help to solve problem?

peerDependencies conflict

My project has dependecies from "vue-class-component": "^7.2.6" and vue-ioc/core has peerDependencies from "vue-class-component": "^6.0.0".

So, when i install package i get

warning " > @vue-ioc/[email protected]" has incorrect peer dependency "vue-class-component@^6.0.0".

Can peerDependencies be upgraded to latest stable major version?

Error in beforeCreate/beforeDestroy hook: "Error: NULL argument"

Hello guys,
Currently I got this error when trying to use Vue ioc.
However, it's not working as expected. I decided to change back to inversify-props temporary
Here's my code:
main.ts

import Vue from "vue";
import { VueIocPlugin } from '@vue-ioc/core'
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "./quasar";

Vue.config.productionTip = false;
Vue.use(VueIocPlugin)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

App.vue

import Component from 'vue-class-component';
import Vue from 'vue'
import AppService from './services/AppService';
import { ProvideReactive } from 'vue-property-decorator';
import { Module, InjectReactive, Inject } from '@vue-ioc/core';
import UserService from './services/UserService';
import Header from './components/parents/Header.vue';

@Module({
  providers:[
    AppService, UserService
  ]
})
@Component({
  components:{
    Header
  }
})
export default class App extends Vue{}

AppService.ts

import { CONFIG } from '@/config/config';
import axios from 'axios';
import { Injectable } from '@vue-ioc/core';

//
// Only use for Desktop's App
@Injectable()
export default class AppService{
    // public readonly AppName = "Cleverdocs";
    isDeskApp: boolean = false;
    public async RequestLicense(): Promise<boolean>{
        if (CONFIG.VIEW_SERVICE){
            const result = await axios.get(`${CONFIG.VIEW_SERVICE}/app/ping`);
            this.isDeskApp = !!result.data;
            return !!result.data;
        }
        this.isDeskApp = false;
        return false;
    }
}

Header.vue

import Component from 'vue-class-component';
import Vue from 'vue'
import AppService from '../../services/AppService';
import { InjectReactive, Inject } from '@vue-ioc/core';

@Component
export default class Header extends Vue{
    @Inject() public appService!: AppService;

    mounted(){
        this.appService.RequestLicense();
    }
}

Here's the message I received:
vue.runtime.esm.js?2b0e:1888 Error: NULL argument
at Lookup.hasKey (lookup.js?0300:61)
at getBindings (planner.js?87b3:123)
at _getActiveBindings (planner.js?87b3:30)
at _createSubRequests (planner.js?87b3:85)
at Object.plan (planner.js?87b3:136)
at eval (container.js?f457:317)
at Container._get (container.js?f457:310)
at Container.get (container.js?f457:230)
at VueComponent.beforeCreate (vue-ioc-core.js?45e1:1)
at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)

As far as I know, it turns out that the var Service Identifier was null when the Inject is invoked.
It happens when beforeDestroy usually even you include "emitDecoratorMetadata": true in tsconfig

Thank you for your help.

providedIn: undefined

Hi!

I use @module with factory function as in readme and get error

Error in beforeCreate hook: "Error: Invalid providedIn: undefined"

Is providedIn required in this case? Or is it issue?

Error disappear if providedIn setted

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.