LHJ
LHJ

Reputation: 692

vue 3 directives listen vue emit

What i want to achieve is to build a loading vue directives,
function with v-loading directive will render spinner and block event till function promise resolve or reject.

What i had tried so far:

  1. use addEventListener, but it can only listen dom's native event, not vue event
  2. hijack vue $emit function, but get a warning said that don't override vue native function named $, even if this solution work, i think this is a bad solution.
  3. in directives argument, binding.instance[binding.value.name] refer to onEvent function in component, i tried override it but it doesn't work. When onEvent trigger again, it run old onEvent before override.
  4. third party event emitter(eg, mitt). this method works well, but custom-component have to write extra code to emit event.
    As example code below,
    user of v-loading have to remember to write 2 emit (mitt and vue's emit).
    It is not that straight forward, and it has extra dependency.
// mitt solution
// custom-component template
<custom-component v-loading:event="onEvent">
// custom-component script
setup(props, {emit}) {
  function emitEvent() {
    emit("event");
    // bad: have to remember to write this extra line, and it is third party dependency
    mittEmitter.emit("event");
  }
}

So, any other solution to listen vue's event(not dom's native event) from vue's $emit?


LoadingDirective.ts

import { Directive } from "vue";

const Loading: Directive = {
  mounted(el, binding) {
    const eventName = binding.arg;
    const onEvent = binding.value;
    // I want to listen vue's event(eventName) here
    // do something extra
    onEvent();  // original onEvent() function to run in App.vue
    // do something extra
  }
};
export default Loading;

App.vue

<template>
  <!-- when onEvent triggered, a spinner will be render in custom-component -->
  <custom-component v-loading:event="onEvent" />
</template>

CustomComponent.vue

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  setup(props, {emit}) {
    function emitEvent() {
      // use only vue's emit
      emit("event")
    }
    return {
      onEvent
    };
  }
});
</script>

Upvotes: 2

Views: 4105

Answers (1)

Matt
Matt

Reputation: 10382

The Vue 3 documentation recommends using an external library such as mitt or tiny-emitter.

JSFiddle Example

<template>
    <div id="app">
        <custom-component v-loading="eventHandler" />
    </div>
</template>
const emitter = mitt();

const customComponent = { template: '<h1>Example</h1>' };

const app = Vue.createApp({
    components: { customComponent },

    setup() {
        setTimeout(() => {
            emitter.emit('loadingEvent', { colour: 'Green' });
        }, 1000);
        
        const eventHandler = e => console.log('Handled!', e);
        
        return { eventHandler };
    },
});

app.directive('loading', {
    mounted(el, binding) {
        const func = binding.value;

        emitter.on('loadingEvent', data => {
            // your logic here...
            func(data);
        });
    },
});

app.mount('#app');

Upvotes: 3

Related Questions