Two-way binding with input value transform

Viewed 92

As the documentation states, v-model directive on input element provides syntax sugar for a combination of bound value prop and input event handler.

Based on this question, an input fails to synchronize with bound value when used with input event handler that transforms a value:

new Vue({
  template: '<input :value="val" @input="onUpdate" />',
  data: () => ({ val: '' }),
  methods: {
    onUpdate(e) {
      this.val = e.target.value.replace(/\s/g, "");
      console.log('val', this.val);
    }
  },
}).$mount('#app')
<script src="https://unpkg.com/vue@2.7.10/dist/vue.js"></script>

<div id="app"></div>

A demo:

demo

The same input works as intended when used with v-model and a watcher, as described in this answer:

new Vue({
  template: '<input :value="val" @input="onUpdate" />',
  data: () => ({ val: '' }),
  methods: {
    onUpdate(e) {
      this.val = e.target.value;
    }
  },
  watch: {
    val: {
      handler(val) {
        this.val = val.replace(/\s/g, "");
        console.log('val', this.val);
      },
      immediate: true
    }
  },
}).$mount('#app')
<script src="https://unpkg.com/vue@2.7.10/dist/vue.js"></script>

<div id="app"></div>

Doing this in the same handler also fixes the problem:

    onUpdate(e) {
      this.val = e.target.value;
      this.val = this.val.replace(/\s/g, "");
    }

What does happen here? Why does the direct assignment of transformed value to val doesn't work, but doing this in two steps does?

This problem applies to both Vue 2 and 3.

1 Answers

I may be wrong, but this is my understanding of what's going on:

With the @input/value pair, if you change the value to be what the value was already, reactivity won't trigger, and so won't change the value in the input.

With v-model, the value will be changed to the new value, (including the space). This will trigger reactivity. Then the watcher will change it back. And when the DOM finally updates, it will set the value of the input.

Using this.val = e.target.value; this.val = this.val.replace(/\s/g, ""); works because you've triggered the reactivity with the first statement.

However, when you use @input, nothing yet has changed the val, and when you set it to remove spaces from the input's target.value, you're setting it to what val was already, and reactivity won't trigger.

It's also worth noting that v-model is only real syntactic sugar when used on a component. When used on a native input, it's not exactly the same. See this issue for example.

Related