Nakamura
Nakamura

Reputation: 1031

Vue - Two way binding for select element using v-bind and props

I have a child component:

select(v-model="selectedItem", @change="emitChange")
  option(:value="{id: '123', value: 'foo'}") 123
  option(:value="{id: '456', value: 'bar'}") 456

data: {
  selectedItem: '',
},
methods: {
  emitChange() {
    this.$emit('change', this.selectedItem);
  },
}

The above one works fine.


But I want to make the value of <select> depends on the parent.

So I do the following:

select(:value="selectedItem", @change="emitChange")
  option(:value="{id: '123', value: 'foo'}") 123
  option(:value="{id: '456', value: 'bar'}") 456

props: ['selectedItem'],
methods: {
  emitChange(e) {
    this.$emit('change', e.target.value);
  },
}

Where parent will catch the event and change selectedItem.

But this doesn't work. e.target.value will be something like [object Object].

What am i missing here?

Upvotes: 4

Views: 7712

Answers (1)

Bert
Bert

Reputation: 82459

e.target is a DOM value, and e.target.value is a string. That's why it comes out as [object Object], which is what you get when you convert your object to a string.

When you use v-model Vue looks for a different property on the element where it has stored the actual javascript object.

That being the case, just use v-model inside your component.

Vue.component("custom-select",{
  props: ['selectedItem'],
  template:`
    <select v-model="selected" @change="emitChange">
      <option :value="{id: '123', value: 'foo'}">123</option>
      <option :value=" {id: '456', value: 'bar'}">123</option>
    </select>
  `,
  data(){
    return{
      selected: this.selectedItem,
    }
  },
  methods: {
    emitChange(e) {
      this.$emit('change', this.selected);
    },
  }  
})

As mentioned in the comments, this option is a little limited, however, in that when the value is set from the outside, the change is not reflected inside the component. So lets fix that.

Vue.component("custom-select",{
  props: ['value', "options"],
  template:`
    <select v-model="selected">
      <option v-for="option in options" :value="option">{{option.id}}</option>
    </select>
  `,
  computed: {
    selected: {
      get(){ return this.value },
      set(v){ this.$emit('input', v) }
    }
  }  
})

Here, we pass the options into the component, and use a computed property with v-model to emit changes. Here is a working example.

console.clear()

const options = [
  {id: '123', value: 'foo'},
  {id: '456', value: 'bar'}
]

Vue.component("custom-select",{
  props: ['value', "options"],
  template:`
    <select v-model="selected">
      <option v-for="option in options" :value="option">{{option.id}}</option>
    </select>
  `,
  computed: {
    selected: {
      get(){ return this.value },
      set(v){ console.log(v); this.$emit('input', v) }
    }
  }  
})

new Vue({
  el:"#app",
  data:{
    selectedItem: null,
    options
  }
})
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script>
<div id="app">
  <custom-select v-model="selectedItem" :options="options"></custom-select>
  <div>
    Selected value: {{selectedItem}}
  </div>
  <button @click="selectedItem = options[0]">Change from parent</button>
</div>

Upvotes: 5

Related Questions