Conditional event binding - vuejs

Update (February 2021)

Several solutions seem to be accepted on Github, whereas my original answer is not. I'm rounding them up here for convenience:

Solution 1a (see Engin Yapici's answer below):

v-on="enableClick ? { click: clickHandler } : {}"

Solution 1b (see Heals Legodi's answer below):

v-on="enableClick ? { click: () => clickHandler(params) } : {}"

Solution 2a (see rvy's answer below and this working demo)

@[eventName]="clickHandler"

Solution 2b (from coyotte508's comment; 2a without the computed property):

@[isClickable&&`click`]="clickHandler"

Solution 3 (mentioned here; seems to be compatible with event modifiers):

@click="enableClick && clickHandler"

Original answer

This works as of Vue 2.6:

<div
  @mouseover="enableMouseover ? mouseoverHandler : null"
  @click="enableClick ? clickHandler : null"
  ...
>

While an event resolves to null, the binding will be removed.

https://github.com/vuejs/vue/issues/7349#issuecomment-458405684

Comments on original answer

It seems to be something I came up with accidentally by misunderstanding that Github thread. But I know from my own testing that it definitely worked back when I posted it. And people continued to upvote it right up to when I made this edit, implying the solution still had some merit. So give it a try if you like.

But after a downvote and a couple of negative comments, I felt the need to improve my answer to save future readers potential headaches. Note that pretty much all the answers to this question refer to the same Github thread, so if you've got time, you might want to go right to the source to learn more. There are many options discussed there, and I suspect more will continue to be posted over time.


Following this discussion it appears the best way to achieve this is to bind v-on to a specification object containing the events you are interested in subscribing to and place your conditionals there like so:

<div v-on="{ mouseover: condition ? handler : null, click: ... }">

Some notes:

  • Passing null for a handler means the underlying addEventLisetener will not happen - which is what we want
  • This means grouping all the event subscriptions into one v-on attribute rather then splitting it into separate and explicit bindings (<div @mouseover='...' @click='...'/>)

  • If this is a long living component and the underlying data changes frequently (leading to rebinding) you should be paying attention to the disposal of the subscriptions (i.e the corresponding removeEventListener) as subscriptions made in one bind pass will not be disposed of on subsequent ones. Evaluate as per your use case...


This is what I'm using:

v-on="condition ? { click: handler } : {}" (Reference)

I get Invalid handler for event "click": got null with v-on="{ click: condition ? handler : null }"


Even more simpler would be to use render functions for that. You won't need to be manually removing the listeners and taking care of them. Also uses simple JS syntax with no mixins.

new Vue({
  el: "#app",
  data: () => ({
    counter: 0
  }),
  methods: {
    handleClick() {
      this.counter++;
    }
  },
  render(h) {
    return h(
      "div",
      IS_MOBILE_DEVICE
        ? {}
        : {
            on: { click: this.handleClick }
          },
      this.counter
    );
  }
});

Full example: https://codesandbox.io/s/nw6vyo6knj


If you want to do something like that you could just apply the event listener manually by adding a ref on the element you want to apply the event to, then using that to bind the event listener in the mounted hook if the condition is met:

Markup

<button ref="button">
  Mouse Over Me
</button>

Vue Instance

new Vue({
  el: '#app',
  mounted() {
    let hasMouse = true;

    // If the user has a mouse, add the event listeners
    if (hasMouse) {
      let button = this.$refs.button

      button.addEventListener('mouseover', e => {
        this.mouseover = true
      })

      button.addEventListener('mouseout', e => {
        this.mouseover = false
      })
    }

  },
  data: {
    mouseover: false
  }
})

Here's a JSFiddle for that: https://jsfiddle.net/0fderek6/

If you don't like that approach, you could also use a directive and place the conditional in there, you could then place that in a mixin to make it reusable:

Mixin

const mouseEvents = {
  directives: {
    mouseEvents: {
      bind(el, binding, vnode) {
        let hasMouse = true;

        if (hasMouse) {
          el.addEventListener('mouseover', e => {
            vnode.context.mouseover = true
          })

          el.addEventListener('mouseout', e => {
            vnode.context.mouseover = false
          })
        }
      }
    }
  },
  data: {
    mouseover: false
  }
}

Vue Instance

new Vue({
  el: '#app',
  mixins: [mouseEvents]
})

Markup

<button v-mouse-events>
  Mouse Over Me
</button>

Here's the JSFiddle for that: https://jsfiddle.net/nq6x5qeq/

EDIT

If you like the directive approach, all you need to do is add an unbind hook to remove the listener, you can then have the binding arg be the event type and the binding value be the handler:

Vue.directive('mouse', {
  bind(el, binding) {
      if (hasMouse) {
        console.log(binding.arg + ' added')
          // bind the event listener to the element
        el.addEventListener(binding.arg, binding.value)
      }
    },
    unbind(el, binding) {
      if (hasMouse) {
        console.log(binding.arg + ' removed')
        el.removeEventListener(binding.arg, binding.value)
      }
    }
});

Now all you need to do is add each listener exactly like you would with v-bind:

<div v-mouse:mouseover="mouseOverFunction"></div>

Here's the JSFiddle to show you how that works: https://jsfiddle.net/59ym6hdb/