Vue add event listener to slot

6.3k Views Asked by At

So I have this <Dialog /> component that extends vuetify's <v-dialog /> default.

In order to avoid having to pass a onClose method to the DialogContent component, I'd rather it $emit('close').

But I can't make my slot listen to this event. :(

Here's the code:

// Dialog.vue

<template>
  <v-dialog
    v-bind="$attrs"
    v-model="dialog"
  >
    <!-- forward other slots -->
    <template
      v-for="(_, slot) of otherSlots"
      v-slot:[slot]="scope"
    >
      <slot :name="slot" v-bind="scope" />
    </template>

    <template v-slot:default="{ on, attrs }">
      <slot name="default" v-on="on" v-bind="attrs" @close="onClose" />
    </template>
  </v-dialog>

</template>

<script>
  import {reject} from '@/utils/object';

  export default {
    inheritAttrs: false,
    computed: {
      otherSlots() {
        return reject(this.$scopedSlots, 'default');
      },
    },
    data() {
      return {
        dialog: false,
      }
    },
    methods: {
      onClose() {
        this.dialog = false;
      }
    },
  }
</script>

Usage:

      <Dialog persistent>
        <template v-slot:activator="{ on, attrs }">
          <v-btn
            v-bind="attrs"
            v-on="on"
          >
            My button
          </v-btn>
        </template>

        <DialogContent />
      </Dialog>

onClose is never called.

Any idea why?

Here's a sandbox to reproduce the issue: https://codesandbox.io/s/pass-event-listener-to-slot-ktemg9

Thanks

2

There are 2 best solutions below

0
On BEST ANSWER

Sooo I finally succeeded to do what I wanted:

Dialog.vue:

<template>
  <v-dialog>
    <slot name="default" :onClose="onClose" />
  </v-dialog>
</template>

Usage:

<template v-slot:default="{onClose}">
  <DialogContent @close="onClose" />
</template>

or to have a more vuetify-like syntax:

Dialog.vue:

<template>
  <v-dialog>
    <slot name="default" :on="{close: onClose}" />
  </v-dialog>
</template>

Usage:

<template v-slot:default="{on}">
  <DialogContent v-on="on" />
</template>

I wish those props (or events) could be forwarded without having to pass them explicitly, but unfortunately this doesn't seem to be possible. :( Otherwise vuetify would do it for its activator slots.

1
On

You problem come from the fact that the $emit emit the function to the parent div, which is your case is the App.vue and not the Dialog.vue.


To solve your problem, you can add a ref to your Dialog and trigger the close function from the div

<div class="text-center my-12">
   <Dialog persistent ref="myDiv">
      <template v-slot:activator="{ on, attrs }">
         <v-btn color="primary" v-bind="attrs" v-on="on"> Click me </v-btn>
      </template>
      <DialogContent @close="onCloseFromParent" />
   </Dialog>
</div>

...

methods: {
    onCloseFromParent() {
      this.$refs.myDiv.onClose();
    },
},