5

I have a Vue 2 pattern I was using for a common scenario: programmatically creating an instance to open a Modal/Dialog/Lightbox on dynamic content outside of a template.

In Vue 2, I found this pattern:

// DialogService.js

export default {
  alert(text) {
    const DialogClass = Vue.extend(DialogComponentDef);
    let dialog = new DialogClass({ propsData: { text } });

    dialog.$on('close', () => {
      dialog.$destroy();
      dialog.$el.remove();
      dialog = null;
    });

    // mount the dynamic dialog component in the page
    const mountEl = document.createElement('div');
    document.body.appendChild(mountEl);
    dialog.$mount(mountEl);
  },
};

How can I acheive this in Vue 3, knowing Vue.extends, $on & $destroy do not exist anymore? You can see a full example of the DialogService.js by clicking here.

4
  • Not a duplicate but related, stackoverflow.com/questions/63471824/vue-js-3-event-bus Commented Oct 27, 2021 at 6:21
  • Actually not related at all. The event bus thing isn't the issue here. Commented Oct 28, 2021 at 11:32
  • It is. You create event bus that can be used programmatically (on and emit methods). Vue 3 doesn't provide such bus, so it needs to be provided externally. The rest could be the same, more or less. 'new Vue' is replaced with 'createApp'. Think of it not as of extended comp, but as of sub-app, because it really is one Commented Oct 28, 2021 at 14:55
  • 2
    Well, createApp doesn't keep the context of the previous app, while Vue.extend did, so createApp isn't the solution anyway here. I changed the title so that it's more explicit. Commented Oct 29, 2021 at 17:52

4 Answers 4

12

Here's how to do with createApp in Vue 3, but the context (stores, plugins, directives...) will not be kept.

// DialogService.js
import { createApp } from 'vue';

export default {
  alert(text) {
    const mountEl = document.createElement('div');
    document.body.appendChild(mountEl);

    const dialog = createApp({ extends: DialogComponentDef }, {
      // props
      text,
      // events are passed as props here with on[EventName]
      onClose() {
        mountEl.parentNode.removeChild(mountEl);
        dialog.unmount();
        dialog = null;
      },
    });

    dialog.mount(mountEl);
  },
};

To keep the context, there's something more complicated that can be seen here with h and render Vue methods : https://github.com/vuejs/vue-next/issues/2097#issuecomment-709860132

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

Comments

3

I would recommend using the mount-vue-component. It's lightweight and easy to use. Code example:

import MyComponent1 from './MyComponent1.vue'
import MyComponent2 from './MyComponent2.vue'
import { mount } from 'mount-vue-component'

let dynamicComponent = mount(someCondition ? MyComponent1 : MyComponent2, { props: { <someProperties...> }, app: MyVueApp })

Comments

2

Here is the simple way to call and run a component programmatically

/* DialogService.js */
import DialogVue from './Dialog.vue';
import { createApp } from 'vue';

const Dialog = (options = {}) => {
  const onClose = options.onClose;
  const tempDiv = document.createElement('div');
  const instance = createApp(DialogVue).mount(tempDiv);

  instance.title = options.title;
  instance.text = options.text;
  instance.onClose = options.onClose;
  instance.show = true;

  document.body.appendChild(instance.$el);
}

export default Dialog;
<!-- Dialog.vue -->
<template>
  <transition name="fade-bottom">
    <h3 v-if="title">{{ title }}</h3>
    {{ text }}
    <button @click="show = false; onClose()">Cancel</button>
  </transition>
</template>

<script setup>
import { ref, defineExpose } from 'vue'

const show = ref(false)
const title = ref('')
const text = ref('')
const onClose = () => {}

defineExpose({
  title,
  text,
  show,
  onClose
})

</script>

1 Comment

I wonder if mount(tempDiv) is necessary. Is there a way to not wrap the component into a surrounding <div></div>?
-1

Vue 3 doesn't provide a generic event bus. It can be replaced with lightweight third-party alternatives like mitt or eventemitter3.

A component can be mounted outside application element hierarchy with a teleport. This has been previously available in Vue 2 with third-party portal-vue library. Modals and other screen UI elements are common use cases for it

<teleport to="body">
  <DialogComponent ref="dialog" @close="console.log('just a notification')">
   Some markup that cannot be easily passed as dialog.value.show('text')
  </DialogComponent>
</teleport>

Where DialogComponent controls its own visibility and doesn't need to be explicitly unmounted like in original snippet. A cleanup is performed automatically on parent unmount:

<teleport to="body">
  <div v-if="dialogState">
    <slot>{{dialogText}}</slot>
  </div>
</teleport>

and

let dialogState = ref(false);
let dialogText = ref('');
let show = (text) => {
  dialogText.value = text;
  dialogState.value = true;
} ;
...
return { show };

For more complex scenarios that require to manage multiple instances, or access show outside components in business logic, a teleport needs to be mounted at the top of component hierarchy. In this case an instance of event bus that can be passed through the application can be used for interaction.

3 Comments

Well my question has nothing to do with event buses. Also, the modal here is just an example of the pattern, the whole pattern here is to be able to instantiate a component when you're not in a template. Teleport is meant to solve things done in a template A that has to appear in a template B. Which isn't the same scenario here.
Well, it could be worded better then. This is a common way to do what you asked about in V3. V2 didn't have teleports to mount a comp outside the app, so you had to jump through hoops by mounting it manually.
I changed the title so that it's more explicit.

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.