senty
senty

Reputation: 12847

Vue 3 Event Bus with Composition API

I have setup mitt and trying to dispatch event to another component but I am having hard time because in the setup() method it doesn't have this for accessing app instance.

Here is what I tried:

import App from './App.vue'
const el = document.getElementById('app')

import mitt from 'mitt';
const emitter = mitt();

const app = createApp(App)
app.config.globalProperties.emitter = emitter;
app.mount(el);

And in the component, I want to dispatch an event

export default {
   setup() {
      function toggleSidebar() {
          this.emitter.emit('toggle-sidebar');

          console.log(this); // binds to setup(), not the vue instance.
      }
   }
}

As this doesn't exist, I can't access the .emitter. What am I missing? How to use officially suggested mitt in Vue 3 composition api?


By the way if I use the v2 syntax, I can access this.emitter. But I am curious about Composition API way

export default {
  mounted() {
    console.log(this.emitter); // works
  }
} 

Upvotes: 11

Views: 18571

Answers (5)

vikramaditya234
vikramaditya234

Reputation: 1398

Solution by @Dan will work, however in the case of typescript you might get 'emitter' is of type 'unknown' error. Solution to this has been provided by @acantepie discussed here, the solution is to use singleton class like:

import mitt from "mitt";

export default mitt()

And use like:

import EventBus from "../lib/EventBus";
...

EventBus.emit(...)
EventBus.on(...)
...

Hope this helps someone like me :)

Upvotes: 0

Voivarum
Voivarum

Reputation: 36

In order to use Event Bus for Composition API you can do the following:

Install and register mitt in your app.js like the TS did:

import App from './App.vue'
const el = document.getElementById('app')

import mitt from 'mitt';
const emitter = mitt();

const app = createApp(App)
app.config.globalProperties.emitter = emitter;
app.mount(el);

After that we'll move the logic for accessing global emitter to a separate file accessEmitter.js. I'll put it into js/Composables folder:

import { getCurrentInstance } from 'vue';

export default function accessEmitter() {
    return getCurrentInstance().appContext.config.globalProperties.emitter;
}

Now it can be utilized in your components:

<script setup>
import accessEmitter from '@/Composables/accessEmitter.js'
const emitter = accessEmitter();

// register listener
emitter.on("myEvent", (data) => {
    console.log('my event');
});
   
// remove listener
emitter.off("myEvent");

</script>

Upvotes: 0

Kez
Kez

Reputation: 199

So far I have used this code to make the "emitter" available.

//main.ts
import mitt from 'mitt'
const emitter = mitt()
export default emitter

And then inside the components I use

import emitter from '@/main';

This worked so far in Vue2 and Vue3 - at least with the options API.

I have to admit though that I currently run into some trouble with the new vite server and the hot module reload (hmr). Is this style suboptimal in any way?

Upvotes: 3

Dan
Dan

Reputation: 63059

To use an event bus in Vue 3 Composition API, use Vue 3's new provide api in main.js, and then inject in any component:

1. Install mitt:

npm install mitt

2. Provide:

main.js

import { createApp } from 'vue';
import App from './App.vue';

import mitt from 'mitt';                  // Import mitt
const emitter = mitt();                   // Initialize mitt

const app = createApp(App);
app.provide('emitter', emitter);          // ✅ Provide as `emitter`
app.mount('#app');

3. Inject

3a. Any Component - Emit an event

import { inject } from 'vue'

export default {
  setup() {
    const emitter = inject('emitter'); // Inject `emitter`
    const mymethod = () => {
      emitter.emit('myevent', 100);
    };
    return {
      mymethod
    }
  }
}

Call mymethod from a button click or something.

3b. Any Component - Listen for the event

import { inject } from 'vue'

export default {
  setup() {
    const emitter = inject('emitter');   // Inject `emitter`

    emitter.on('myevent', (value) => {   // *Listen* for event
      console.log('myevent received!', `value: ${value}`);
    });
  },
}

Console

myevent received! value: 100 

Upvotes: 36

Daniel
Daniel

Reputation: 35684

You may be able to use getCurrentInstance to get the global property

component:

import { getCurrentInstance } from 'vue';
export default {
  setup() {
    // get current instance
    const internalInstance = getCurrentInstance(); 
    // get the emitter from the instance
    const emitter = internalInstance.appContext.config.globalProperties.emitter;
  }
} 

Upvotes: 2

Related Questions