Kasheftin
Kasheftin

Reputation: 9443

Check if a component has an event listener attached to it

Assuming there's some <Form> component. It can be called with a @cancel event listener attached to it and if it's the case, I want to show the cancel button that triggers this event. If there's no @cancel event, the cancel button should not be visible.

Is there a way to check if a component has event listener attached to it?

Currently I do:

<template>
  <form>
    <button v-if="cancelEventPassed" @click="$emit('cancel')">Cancel</button>
  </form>
</template>

And call it like this:

<Form :cancelEventPassed="true" @cancel="handle_cancel" />

either

<Form/>

Is it possible to achieve this without using any additional property like cancelEventPassed?

Upvotes: 44

Views: 30908

Answers (10)

Gert Rikkers
Gert Rikkers

Reputation: 86

After struggling for quite a long time and reading several comments it finally works in Vue3 (3.2.41) with TypeScript. I should implement it like this:

<template>
  <form>
    <button v-if="cancelEventPassed" @click="$emit('cancel')">Cancel</button>
  </form>
</template>

<script setup lang="ts">
  import { getCurrentInstance, ref } from "vue"
  const emit = defineEmits(['cancel'])
  const cancelEventPassed = ref(!!getCurrentInstance()?.vnode.props?.onCancel)
</script>

And call it like this:

<Form @cancel="handle_cancel" />

Upvotes: 0

yycking
yycking

Reputation: 1311

Thanks answer from @abdul-aziz-al-basyir .
In Vue 3, use attrs to replace emit and listeners

<script setup>
const attrs = useAttrs()
const cancelEventPassed = computed(() => {
  return !!attrs.onCancel
})
function toCancel() {
  attrs.onCancel()
}
</script>
<template>
  <form>
    <button v-if="cancelEventPassed" @click="toCancel">Cancel</button>
  </form>
</template>
<Form @cancel="handle_cancel" />

Upvotes: -2

Abdul Aziz Al Basyir
Abdul Aziz Al Basyir

Reputation: 1246

In Vue 3

const thisInstance = getCurrentInstance();

const hasTaskClickedListener = computed(() => {
  if (!thisInstance) return null;

  return thisInstance.attrs?.onClicked;
});

explainations:

  • this is same like getCurrentInstance() in vue 3
  • listeners object was changing by attrs with prefix "on" like onClick, onDelete and so on
  • put getCurrentInstance() always on setup script unless you will got always null (the value when vue can't see their instance)

Upvotes: 1

Robin Schambach
Robin Schambach

Reputation: 846

Vue 2.7 script setup example

import {useListeners, computed} from 'vue';

const listeners = useListeners();
const hasCancelListener= computed(() => {
    return typeof listeners['cancel'] !== 'undefined';
});

Upvotes: 0

Simon
Simon

Reputation: 2986

Since @prerak-sola's solution doesn't work if you're defining emits like this (as noted by Adam Reis):

const emit = defineEmits<{
  (e: 'click', v: MouseEvent): void;
  (e: 'update:modelValue', v: MouseEvent): void;
}>();

I figured out, since vue converts all props to an object and just adds a prefix on before each event-name, you can just check if the property (the event listener) is defined in the vnode:

const hasClickEventListener = computed(() => !!getCurrentInstance()?.vnode.props?.onClick);
const hasModelValueUpdateListener = computed(() => !!getCurrentInstance()?.vnode.props?.['onUpdate:modelValue']);

I, however, couldn't find anything about this in the official documentation (and it's much more difficult than using useAttrs). So use with caution.

Upvotes: 13

Abdul Aziz Al Basyir
Abdul Aziz Al Basyir

Reputation: 1246

This example for Vue 3

this.$attrs.onCancel

Explanation

Vue 3 has removed the $listeners object instead listener will be parted from the $attrs object with the on.. prefix.

Reference

https://v3-migration.vuejs.org/breaking-changes/listeners-removed.html#overview

Upvotes: 3

Prerak Sola
Prerak Sola

Reputation: 10009

In Vue 3, the $listeners object has been removed. The listeners are now part of the $attrs object and are prefixed with on.

In order to check if a particular listener is present or not in a child component, you can do:

computed: {
  hasCancelListener() : boolean {
    return (this.$attrs && this.$attrs.onCancel) as boolean
  }
}

The child component is called as:

<custom-form @cancel="onCancel"></custom-form>

Upvotes: 17

wobsoriano
wobsoriano

Reputation: 13434

If you're here looking for Vue 3 script setup or setup function solution, you can check the attrs key in getCurrentInstance function

<template>
  <form>
    <button @click="$emit('cancel')">Cancel</button>
  </form>
</template>
<custom-form @cancel="onCancel"></custom-form>
onMounted(() => {
  const instance = getCurrentInstance() // only available inside lifecycle hooks
  console.log(instance?.attrs?.onCancel)
})

Upvotes: 0

Tadas Majeris
Tadas Majeris

Reputation: 371

You can check if listener exists like that: this._events['listener-name']

Upvotes: 0

Bert
Bert

Reputation: 82459

When there are listeners attached to a component they are available in the $listeners property of the component.

You can use that property to determine if a specific listener is available. For example, here is a computed property that checks for the existence of a cancel listener.

computed:{
  hasCancelListener(){
    return this.$listeners && this.$listeners.cancel
  }
}

And here is an example of that used in a component.

console.clear()

Vue.component("CustomForm", {
  template:`
    <div>
      <h1>Custom Form</h1>
      <button v-if="hasCancelListener" @click="$emit('cancel')">I have a listener!</button>
    </div>
  `,
  computed:{
    hasCancelListener(){
      return this.$listeners && this.$listeners.cancel
    }
  },
})

new Vue({
  el: "#app",
  methods:{
    onCancel(){
      alert('canceled')
    }
  }
})
<script src="https://unpkg.com/[email protected]"></script>
<div id="app">
  <custom-form @cancel="onCancel"></custom-form>
  <hr>
  <custom-form></custom-form>
</div>

Upvotes: 62

Related Questions