1

I am writing a small wrapper for a <input> component. I was following the documentation and this tutorial.

However I am having issues with the two-way binding. I expect my component to work with v-model. The binding seems to work fine for updating the value, but when I change the value itself the component doesn't update. I've also read about transparent wrappers, which seems to be my issue here.

Simple example:

<template id="numInput" :v-on="listeners">
  <div>
    <!-- note the div here, it works without, but that's not what I want -->
    <input ref:inp type="number" :value="num" @input="updateSelf($event.target.value)" @blur="reset" :class="{rc: isNegative}" />
  </div>
</template>

You can find the whole code example here.

The binding works one way (modifying the text input). But the input component doesn't get updated the other way (button klick in example)

Any help is welcome!

2 Answers 2

2

Custom input components should be stateless (unless you have a reason not to); what this means is the value of the component should be provided by a prop and not local state (data). Your <num-input> component lacks a value prop which is needed for v-model to work.

Also you might want to set the inheritAttrs option to false and bind $attrs manually since you've wrapped the input element in a div.

Here's a small example of how it should be:

Vue.component('num-input', {
  template: '#numInput',
  inheritAttrs: false,
  props: ['value'],
  computed: {
    isNegative() {
      return this.value < 0;
    },
  },
  methods: {
    onInput(e) {
      // You can conditionally do this after validation
      this.$emit('input', e.target.value);
    },
    onBlur() {
      if (this.isNegative) {
        this.$emit('input', 0);
      }
    },
  },
});

new Vue({
  el: '#app',
  data: {
    num: 0,
  },
});
.negative { color: red; }
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>

<div id="app">
  <num-input v-model="num"></num-input>
  <button @click="num++">+1</button>
  <p>{{ num }}</p>
</div>

<template id="numInput">
  <div>
    <input type="number" :value="value" @input="onInput" @blur="onBlur" :class="{ negative: isNegative }" v-bind="$attrs">
  </div>
</template>

I haven't coerced the value emitted by the input event to a number (it's a string), but I'll leave this up to you.

Sign up to request clarification or add additional context in comments.

1 Comment

Great! Thanks also for the explanation!
0

You have a couple of things going wrong here, but the most fundamental is that you are mixing nom and num in your code. I reworked your component a little and turned your num data into a property and then bound to it in your main app.

Here is your reworked code...

Vue.config.devtools = true;

// global component registration
Vue.component('num-input', {
  template: '#numInput', 
  props: ['value'],
  computed: {
    isNegative: function() {
      console.log('num: ' + this.value)
      return this.value < 0;
    },
    listeners: function () {
      return this.$listeners;
    },
  },
  methods: {  
    reset () {
      if (this.isNegative) {
        this.$emit('input', 0)        
      }
    }
  },
});

 new Vue({
   el: '#container',
   data: {
     num: 0,
   },
   methods: {
     increaseBy1() {
       this.num++;
       console.log("increased by 1");
     },
   },
 });

To view the code and the html binding I forked your codepen and it can be found at...

https://codepen.io/peterpde/pen/BOVQzg

3 Comments

In your example, changing the input doesn't change the p tag. You also have to add this piece of code : @input="num = $event" into num-input. There also something else that is wrong : the updateSelf mutates the num prop.
You are right. I was too focused on getting the button to work. Let me look at again and make edits. Thanks.
Changed code to fix the many issues pointed out my @ThomasLombart. Thanks again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.