3

I'm trying to make a wrapper component for an <input/> element in Vue.js.

Component:

<template>
  <div>
    <input v-bind="$attrs" :value="value" @input="input" />
    ...
  </div>
<template>

Vue.component("my-input", {
   inheritAttrs: false,
   props: ['value'],
   methods: {
     input($event): void {
       this.$emit("input", $event.target.value)
     }
  }
})

Usage:

<my-input v-model="myModel" />

This seems to work just fine. The model gets updated via the input event handler by emitting the target element value.

However, now I'm trying to use this component with some existing code:

<my-input v-model="myModel2" @input="something = $event.target.value" />

This is where I'm having trouble with the $emit("input") event. I'm getting the following error:

Cannot read property 'value' of undefined

So, my $emit is emitting the value and now the existing @input="something..." event handler can't reference the $event properly.

If I change my component's input method to emit the $event instead of $event.target.value, the new code seems to work, but then model doesn't get updated gets updated to the InputEvent instead of the actual value.

I'm not sure what I need to do.

0

3 Answers 3

5

When you $emit('input') and the value is tied to a v-model of a text input, the value of the <input> will be updated to whatever you emitted. In the case of $emit('input', $event.target.value), it's the value of the text in the <input> that you are emitting. This value will be intercepted in the parent, as v-model effectively does: <my-input :value="inputValue" @input="inputValue = $event">

This means the value of the <input> will get bound back to the <input> (effectively causing no change to the value in the input). If, however, you $emit('input', $event), then v-model will still capture whatever value is passed up and update the value of the <input> with it. In this case, it will be the actual input event object, as you've said.

If you don't want to use the input event tied to your model, you could always use a custom v-model event. Then you'd be able to $emit('input', $event) and not have it affect the v-model value, instead you'd update the v-model from $emit('custom-event', $event.target.value)

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

3 Comments

Okay, I just started looking at that. Essentially you're saying I update my component to include model: { prop: "value", event: "custom-event" } and then in my component's input method I have to emit both input and custom-event with the correct $event or $event.target.value. I think it seems to be working. For some reason one of my fields isn't updating until the second key press, but I think that is another issue.
@Quantastical yes, you'd need two separate $emit events to properly keep your values in sync I believe, but I think that's the only way you'd be able to keep existing code the same outside of <my-input>
@Quantastical edit the question or ask a new one and I can look at the other issue if you can't figure it out
2

Try

<my-input v-model="myModel2" @input="value => something = value" />

4 Comments

I'm trying to make my component a drop-in replacement for the old input fields. Basically don't want to mess with the old events and code other than changing the component to my custom one for design purposes.
You're emitting the value, not the event, so @input is receiving just the value, not $event. That's why you're getting the error: value is undefined. You can't have it both ways
I understand that. However, if I emit the event, then the model gets messed up. Are you suggesting that I can't create a custom component that uses this emitter to update the model appropriately in addition to having a typical input event handler?
I believe that is the case, you would have to update your existing code to the above, or emit something else @update for example, but that would also require you to update your existing code, I'm not really sure what other options you have.
2

Just assign them from parent directly instead of creating proxies

const MyInput = Vue.extend({
  name: 'MyInput',
  template: '#ins',
  data(){return{valid: true}},
  methods: {validate(ev){this.valid = ev.target.value.length < 1 ;this.$listeners.input(ev)}}
})

const App = Vue.extend({
  components: {
    MyInput
  },
  template: '#myinput',
  data(){return{val: 'test'}},
  methods: {ins(ev){console.log(ev.target.value)}}
})

new Vue({
  name: 'root',
  render: h => h(App)
}).$mount("#app");
input {background: red}
.valid{background:green !important}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

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

<template id="ins">
  <input v-bind="$attrs" @input="validate" :class="{valid:valid}"/>
</template>

<template id="myinput">
    <my-input v-model="val" @input="ins" />
</template>

2 Comments

Thanks. That does make sense. My simplified example left out an important detail that prevents me from doing this...my custom component needs to be able to call its own internal method on input (for handling validation stuff) in addition to whatever event handler might be passed in. This is a valid answer without those details, though.
@Quantastical changed it

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.