How to emit an event from a nuxt plugin?

Viewed 2337

Code:

export default ({ app }, inject: (key: string, value: any) => void) => {
  // doesn't work
  app.$emit('eventname', 'value')
})

I want to emit an event from a plugin from the component.

app.$emit() throws an error app.$emit is not a function

2 Answers

After look at the nuxt plugins documentation, I found a possible solution to this problem. I defined my plugin as follow

plugins/hello.ts

import { Context } from '@nuxt/types';
import Vue from 'vue';

export default function (_ctx: Context, inject: Function) {
  const hello = function (this: Vue, msg: string) {
    console.log('emitting', msg);

    if (process.server) {
      console.log('server side');
    } else {
      console.log('client side');
    }

    setInterval(() => {
      this.$nuxt.$emit('hello', msg);
    }, 5000);
  } // Event Bus 
   .bind(new Vue());

  inject('hello', hello);
}

Pay attention that I used anonymous function(...){} and not an arrow function () => {...}. Updated don't forget to bind(new Vue()) the event bus otherwise if you call this.$alert in your vuex store this will be an instance of Store and not Vue as expected.

And I'm using it as the follow

page/something.vue

...
mounted() {
    this.$hello('test');
    this.$nuxt.$on('hello', (val: string) => {
      alert(val);
    });
},
...

And it works as expected! As I'm using typescript I need to defined this as Vue to avoid this.$nuxt is not defined error.

And in my nuxt.config.js

...
 plugins: [
    // '~/plugins/axios'
    { src: '~plugins/vuedraggable.ts' },
    { src: '~plugins/hello.ts' },
  ],
...

I hope this help you somehow.

Updated:

If you are using typescript and wants to merge (Module Augmentation) $hello so it becomes visible in both Vue instance, Context and Vuex store, you can include this piece of code in the same file of your plugin in our case plugins/hello.ts

declare module 'vue/types/vue' {
  // Vue instance this.$hello
  interface Vue {
    $hello(msg: string): void;
  }
}

declare module '@nuxt/types' {
  // NuxtAppOtions this.app.$hello
  interface NuxtAppOptions {
    $hello(msg: string): void;
  }
  // Accessible by Context
  interface Context {
    $hello(msg: string): void;
  }
}

declare module 'vuex/types/index' {
  // this.$hello inside Vuex stores
  interface Store<S> {
    $hello(msg: string): void;
  }
}

And that is it.

My implementation with typescript

Create a global eventBus using Vue

plugins/event-bus.ts

import Vue from 'vue'
import { Plugin } from '@nuxt/types'

declare module 'vue/types/vue' {
  interface Vue {
    $bus: Vue
  }
}

declare module '@nuxt/types' {
  interface NuxtAppOptions {
    $bus: Vue
  }
  interface Context {
    $bus: Vue
  }
}

declare module 'vuex/types/index' {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  interface Store<S> {
    $bus: Vue
  }
}

const eventBus = new Vue()

const eventBusPlugin: Plugin = (_, inject) => {
  inject('bus', eventBus)
}

export default eventBusPlugin

Don't forget to declare your plugin in nuxt.config.ts. It must be in the first place like below:

...

  plugins: [
    '~/plugins/event-bus', // <--- here
    '~/plugins/filter',
    '~/plugins/analytics',
    '~/plugins/sentry',
    '~/plugins/vuetify',
    '~/plugins/copy-clipboard',
    '~/plugins/vee-validate',
    '~/plugins/moment',
    '~/plugins/icons',
  ],

...

Using eventBus in other plugin plugins/analytics.ts

import { Plugin } from '@nuxt/types'

const analytics: Plugin = ({ $bus }) => {
  $bus.$on('mark-has-payed', (args: any) => console.log(args))
}
export default analytics

Using eventBus in component components/MyComponent.ts

<template>
 ...
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'nuxt-property-decorator'

@Component
export default class extends Vue {
  markHasPayed() {
    this.$bus.$emit('mark-has-payed', { payment: 'Visa' })
  }
}
</script>

Related