Calling a global popup from nested vue components

I have tons of nested Vue components that need to be able to open a global modal. To avoid having tons of listeners and emitters snaking their way all through the app, I am using VueX. The main App has a modal which gets shown if the modal state is not undefined.

Root app template

<template>
<components></components>
<popup v-if="popupImage" :image="popupImage"></popup>
</template>

Root app script

import { State } from 'vuex-class'
@State(state => state.popupImage) popupImage:string

Nested component

From within a component I set the popup string, which will trigger the global modal:

this.$store.commit("setPopup", "bla.jpg") It all works fine, but does it seem a bit overkill to add VueX ONLY for a modal window? The rest of the app doesn't use VueX at all. What is the recommended way to open a modal from anywhere in a Vue app? • I would add more code in future questions in order to provide more context of what happens. As the use of Typescript isn't the norm when using Vue I would also mention this (from what I can determine this is Typescript, right?). – AnotherGuy Feb 4 '18 at 1:34 • Yes it's typescript, although my question is more about workflow than syntax. VueX does a lot under the hood so I was wondering how others would implement a modal. – Kokodoko Feb 4 '18 at 11:58 2 Answers Your solution seems to work and would not create unnecessary confusion for other developers (or yourself in six months). And even though Vuex seems overkill currently it may be beneficial later. One thing which I am unsure about is how the modal is closed. I assume this is handled by the component displaying the image, by setting the value in the Vuex store to an empty string or NULL. In case you want to avoid using Vuex you could use a simple event bus implementing a publish/subscribe pattern. This can be built using Vue itself. An example is: const EventBus = new Vue(); This can be attached to the window object or stored in a separate file and included the same way as components are. You can then a mounted hook/method of your Vue component which handles the modal do: Vue.component('modal-component-name', { ... mounted() { EventBus.$on('event-name', () => { ... });
}
});

This will listen for events with the name: 'event-name'. Then to trigger the event do the following in another component:

Vue.component('component-which-trigger-modal', {
...
methods: {
openModal() {
EventBus.$emit('event-name'); } } }); This also supports providing arguments when the event is triggered. Keep in mind the event callback is not removed when the components is destroyed. To ensure multiple listener callbacks doing the same thing isn't attached when a component is loaded again use the Vue.$off() method when the component is destroyed. This can be done by implementing the destroyed method on the component instance, which is called just before the component is destroyed. This requires you keep a reference to the callback used, so you cannot pass an anonymous method like I have done in the example.

A simple tutorial for the event bus can be found here: Creating a Global Event Bus with Vue.js.

• Thanks, I wasn't aware of the Event Bus, it seems exactly right for this situation. Yes, I'm closing the popup by setting state.popupImage to undefined. – Kokodoko Feb 4 '18 at 11:54

Global Event Bus is a solution but since you have to listen emits in components's mounted or created lifecycle hook, it's getting duplicated in each instance.

I had same 'issue' and resolved it by mounting my popup component dynamically and setting it into prototype of my Vue instance.

Example;

import Vue from 'vue';
import Popup from '@/components/Popup.vue';

Vue.prototoype.$popup = new Vue(Popup).$mount();

Imagine you have show method in your Popup component. Now with this injection you can use it in any component as;

this.\$popup.show();
• That's very interesting, it just makes me wonder what the default workflow is for using popups in a Vue app. Perhaps popups are meant to be local to the component that calls them? – Kokodoko Nov 1 '18 at 11:49