Home > Back-end >  How to access global Vue instance to programmatically added components in Vue 3
How to access global Vue instance to programmatically added components in Vue 3

Time:06-20

Imagin a simple custom component like this: MyComponent.vue

<template>
  <p>
    <div>Test {{ message }}</div>
    <div ref="container"></div>
  </p>
</template>

<script>
export default {
  data() {
    return {
      message: ""
    };
  },
  mounted() {
    console.log(this.$store);
  },
  methods: {
    show(message) {
      this.message = message;
      console.log(this.message);
      console.log(this.$refs.container);
    },
  },
};
</script>

I could use it like this in an other template:

<template>
  <MyComponent />
</template>

<script>
import MyComponent from "./MyComponent";
export default {
  components: {
    MyComponent,
  },
};
</script>

This is working fine. Now I would like to add this component programmatically.

import { h, render } from "vue";

const vnode = h(MyComponent);
let el = document.createElement("div");
document.body.appendChild(el);
render(vnode, el);
vnode.type.methods.show(message);

This works, but I cannot access globally added instance like veux store, router, registered components and directives. It looks like it renders on a own Vue instance. How add the global vue instance to this? Also the message gets not updated via the show() method. Also this.$refs.container is undefined.

Has anyone an idea to do this the right way? I was able to do this using Vue2, but that is not working in Vue3

CodePudding user response:

You can get as many apps as you want to use the same store. Have them import the same store instance from a location they can both access. Example:

const { createApp, toRefs, defineComponent } = Vue;
const { createPinia, defineStore } = Pinia;

const pinia = createPinia();

const useStore = defineStore('my-store', {
  state: () => ({ counter: 0 })
})

const App1 = defineComponent({
  setup() {
    const store = useStore();
    return {
      add: () => store.counter  ,
      sub: () => store.counter--
    }
  }
})

createApp(App1)
  .use(pinia)
  .mount('#app-1');

const App2 = defineComponent({
  template: `<h1>App 2</h1>
  <span v-text="counter"></span>`,
  setup: () => ({ ...toRefs(useStore())})
})

// let's delay creation of second app by a couple of seconds:
setTimeout(() => {
  createApp(App2)
    .use(pinia)
    .mount('#app-2')
}, 2e3);
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/vue-demi"></script>
<script src="https://unpkg.com/[email protected]/dist/pinia.iife.prod.js"></script>

<div id="app-1">
  <h1>App 1</h1>
  <button @click="add">Add</button>
  <button @click="sub">Subtract</button>
</div>

<div id="app-2">
  <br>
  App2 not loaded yet..
</div>

Notes:

  • const { ...stuff } = Vue becomes import { ...stuff } from 'vue'. Same for 'pinia' imports.
  • const pinia = createPinia() becomes import { pinia } from './shared-location'. At './shared-location' you should have export const pinia = createPinia()

Both apps react to changes on the shared pinia instance. When either app adds a store to pinia, the other one gets access to the store and will be able to use it normally.
If using TypeScript you might need to use declare to type your stores.

CodePudding user response:

@tao Thanks for your idea. I have made another solution. I have made dynamic component wrapper like this:

<template>
  <component
    v-bind="currentProps"
    :is="currentComponentName"
    ref="currentComponent"
  />
</template>
<script>
import MyComponent from "./MyComponent";

import { getCurrentInstance } from "vue";

export default {
  components: {
    MessageDialog,
  },

  data() {
    return {
      currentProps: {},
      currentComponentName: undefined,
    };
  },

  created() {
    let p = getCurrentInstance().appContext.config.globalProperties;
    p.showMyComponent: this.showMyComponent,    
  },

  methods: {
    async showMyComponent(message, extraProps) {
      this.currentComponentName = "MyComponent";

      await this.$nextTick(() => {
        //wait untill next tick, else not access to  this.$refs.currentComponent
      });
      return this.$refs.currentComponent.show(message);
    },
  },
};
</script>

From every where in my app I could call this.showMyComponent() and it will show it.

  • Related