Vue: Conditional event binding

Created on 31 Dec 2017  ·  34Comments  ·  Source: vuejs/vue

What problem does this feature solve?

Right now if we want an event handler to run conditionally we need to place the condition in the event handler which means that the event is still subscribed to and we pay the memory allocation fee for the event subscription (the underlying addEventListener and corresponding handler).

In some scenarios this is a pain. For example: say I've got mouse events (mouseover, mouseout) that are only meaningful on devices that actually have a pointer/mouse and so are meaningless on mobile and touch devices.

Right now, I'd have to create the event subscriptions and add the condition in the handler (or in this case no need even for that as the events would never fire) BUT I've still attached these handlers and allocated memory for them - which especially on memory constrained platforms like mobile browsers is a waste.

By making the event subscription itself conditional we can avoid this.

What does the proposed API look like?

In its simplest form we could just examine the handler provided and if falsy (or just null) short-circuit the event subscription and NOT apply the underlying addEventListener operation.

This way the condition can appear in the event binding declaration itself:

<div @mouseover="condition ? handler : null" /> 
feature request has PR

Most helpful comment

@Kingwl that still adds the listener, which is what OP wants to avoid.

@sqal 's suggestion is actually a valid workaround, and can be simplified to:

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

All 34 comments

@asiFarran

Right now, I'd have to create the event subscriptions and add the condition in the handler (or in this case no need even for that as the events would never fire)

Actually there's another, simple way how you can handle this case. You can pass computed property with your listeners object (or null if condition is not met) to v-on, example: https://jsfiddle.net/c0Le92xe/

maybe you need ?
<div @mouseover="e => condition && handler(e)" />

@Kingwl that still adds the listener, which is what OP wants to avoid.

@sqal 's suggestion is actually a valid workaround, and can be simplified to:

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

Yes, @sqal's solution is a good one and with inlining like @yyx990803's shows is close enough to what I had in mind. Thanks!

This leads me to a deeper issue though: If a rebind happens (data change) and upon evaluation the condition changes (or more generally if the event spec object passed to v-on is different), existing event subscriptions are not cleaned up. They will still (hopefully) be disposed at the end of the component's lifecycle but not when they 'should' which is when v-on rebinds.

This may be an edge case that does not have a large impact for most scenarios but just for reference:

In my scenario I have a complex SVG and need to (optionally) attach mouse over/out handlers to specific elements based on some logic.
The component is a long lived one and the underlying data changes leading to rebinding where I need to attach the event handlers to different elements at each rebind - hence my need to dispose of the previous subscriptions so they don't dangle orphaned and sad and drink memory.

A typical solution - and what I'm going to end up doing - is to set up one 'top level' event listener (per event type) and let events bubble up to it but to explain my motivation for trying it the Vue way first, there's quite a bit of processing that has to happen in my handlers which I had intended to pre-calculate and bake right into the event subscriptions itself so that the handlers have less to do (quicker and more fluid response) and are not invoked at all for elements that don't require it (as opposed to filtering this out relying on DOM queries in the handler).

@VsevolodTrofimov the whole point was to avoid the event subscription from happening at all. Applying the condition in the handler does not satisfy this requirement. Please note the comments above as they do provide a satisfactory resolution.

I am closing this issue to reflect that.

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

Is it possible to pass $event and other arguments to handler?

@pmayer
<div v-on="{ mouseover: condition ? $event => handler($event, arg) : null }">

Or curry the handler and use

<div v-on="{ mouseover: condition ? handler(arg) : null }">

With the suggested solution

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

Is there a way to apply modifiers like ".stop.prevent"?

Hey @yyx990803 @VsevolodTrofimov

The solution suggested here (using v-on="{ mouseover: condition ? handler : null }") is not really working with latest Vue.

I'm getting this error:
Invalid handler for event "mouseover": got null

So looks like Vue is actually trying to fire the handler instead of unbinding the event 🤔 .

@DawidMyslak
Just change it to the below and it will work

v-on="condition ? { mouseover: handler } : {}"

or, if your handler is called mouseover

v-on="condition ? { mouseover } : {}"

Nice one @pbastowski !

Thank you guys!

You may want to wrap with inline function if you are calling a handler with custom data. Something like this

v-on="condition ? { mouseover: () => handler(somedata) } : {}"

This should be in Vue docs

@DawidMyslak
Just change it to the below and it will work

v-on="condition ? { mouseover: handler } : {}"

or, if your handler is called mouseover

v-on="condition ? { mouseover } : {}"

Is there a way to combine this with .once?

You can use https://vuejs.org/v2/guide/render-function.html#Event-amp-Key-Modifiers

<button v-on="{ '~click': () => foo = new Date() }">Trigger only once</button>

One question on this, will there be memory leaks or event listeners not removed when I do the following, and the condition regularly changes?

<template>
<div  v-on="myListeners">
some content
</div>
</template>
<script>
...
computed: {
  myListeners() {
    return this.canExecute ? { click: () => this.$emit(...) } : {};
  },
},
...
</script>

So in short, does the v-on handles the change or not?

@dietergeerts it does.

Does this work with native events? I couldn't get it working with keydown.native.

FYI. Since 2.6 (not released yet) you will be able to apply a conditional event binding as follows:

<div @[event]="handler" /> 

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

@Justineo That's great, but I just tried the beta and it doesn't seem to work with modifiers (of any type) either. Has that not been implemented yet? Is it planned?

@AlansCodeLog it should work with all modifiers. If it's not working you should open a new issue with a reproduction.

@yyx990803 Okay, I'll see if I can reproduce it.

I have opened an issue here: #9417

maybe you need ?
<div @mouseover="e => condition && handler(e)" />

Browser JavaScript version

<div @mouseover="condition && handler(arguments[0])" />

@kieryk123 Can you provide a CodeSandbox link to look at?

In my case, I was trying to do this on a disabled button element (via Vuetify), but apparently Chrome doesn't fire the necessary events, and the element should be wrapped in a div or similar to catch the event.

Hello.
I tried to do it like this, but it does not work.

// not working
v-on="{ [condition ? 'click.stop' : 'click'] : eventfunc }"
// or
// error
v-on="{ condition ? 'click.stop' : 'click' : eventfunc }"

Is there any other solution?
Thanks in advance!

Here is the trick for native events (using Vue v2.6.11):

@mouseenter.native="condition && handler($event)"

<div @[event]="handler" />

@Justineo was this syntax added to Vue?
I can't get it to work. Maybe you could send a link to docs where I can read more about it?

@proArtex thank you, works for me as well!
@keyup.delete="title.length === 0 && cancelTopicCreation()"

Question is.... why?

@AndrewBogdanovTSS have a look at this fiddle https://jsfiddle.net/pbastowski/v0wt5qpo/27/

The syntax works just fine for normal and native clicks. Note that .native events are only available on Vue components and not plain HTML elements.

@pbastowski simple string is working, yes, but what I need is to be able to resolve the value of event based on some reactive data, so I need something like

<h2 @[() => someProp ? 'mouseup' : null]="alert('Normal click on an HTML element')">H2 Element - click it</h2>

and such syntax doesn't work for me

@AndrewBogdanovTSS Just move your logic to a computed, see the updated example here https://jsfiddle.net/pbastowski/v0wt5qpo/63/

Was this page helpful?
0 / 5 - 0 ratings