2

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

1
  • The code with vnode exists outside Vue instance. Consider explaining your case, it looks like XY problem. "vnode.type.methods.show" - it's a mistake to call it from methods. Commented Jun 17, 2022 at 10:01

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

Comments

1

@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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.