MaYaN
MaYaN

Reputation: 7006

How can I use v-model with a functional template component in Vue?

Vue.js has an example demonstrating how to build a functional component wrapping a button:

<template functional>
  <button
    class="btn btn-primary"
    v-bind="data.attrs"
    v-on="listeners"
  >
    <slot/>
  </button>
</template>

But how can I build a similar component working with select which I can then use v-model on its parent?

I have come up with the following, but when I use v-model on it like so:

<MyComponent :Label="'Status'" :Data="Statuses" v-model="selectedStatus" />

The value of selectedStatus becomes:

[object Event]
<template functional>
    <select v-bind:value="data.attrs" v-on="listeners">
        <option>-</option>
        <option v-for="item in props.Data" :key="item" :value="item">{{item}}</option>
    </select>
</template>

<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";

@Component
export default class TaskSelector extends Vue {
  @Prop({ type: String, required: true })
  public Label!: string;

  @Prop({ type: Array, required: true })
  public Data!: string[];
}
</script>

Upvotes: 2

Views: 2166

Answers (1)

tony19
tony19

Reputation: 138586

For v-model to work properly, the component must receive a value prop and emit an input event whose data is a string value. But the <select>'s native input event data is an InputEvent object, converted to a string ([object Event]), which yields the incorrect result with the bound v-model.

To fix this, we need to modify the <select>'s input event data before it reaches the parent. This requires removing <select v-on="listeners">, which would've allowed the problematic native input event to propagate to the parent. We then use listeners.input($event.target.selectedOptions[0].value) to forward the input event with the selected option's string value.

Steps:

  1. Add a value prop, and bind the <select>'s :value to the value prop
  2. Re-emit the <select>'s input event with the selected option's value. This should replace v-on="listeners".

Your SFC should look like this:

<template functional>
  <select
    1️⃣:value="props.value"
    2️⃣@input="listeners.input && listeners.input($event.target.selectedOptions[0].value)"
  >
    <option value="">-</option>
    <option v-for="item in props.Data" :key="item" :value="item">{{item}}</option>
  </select>
</template>

demo

Upvotes: 5

Related Questions