Search code examples
typescriptvue.jsvuejs3vue-componentvue-options-api

(Vue3) Link form component object with parent object using v-model and Options API?


My question is simple. I am following the official Vue documentation explaining v-model arguments in relation to forms in a child component. Here's my code:

(App.Vue)
<script>
import UserName from './UserName.vue'

export default {
  components: { UserName },
  data() {
    return {
      firstLast: {
        first: 'John',
        last: 'Doe'
      }
    }
  }
}
</script>

<template>
  <h1>{{ firstLast.first }} {{ firstLast.last }}</h1>
  <UserName
    v-model:first-name="firstLast.first"
    v-model:last-name="firstLast.last"
  />
</template>
(UserName.vue)
<script>
export default {
  props: {
      firstName: String,
    lastName: String
    },
  emits: ['update:firstName', 'update:lastName']
}
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

The above two files work! John Doe is displayed on screen, and whatever you put in the inputs will change the value of the name.

My issue is that in UserName.vue, I am referencing two separate variables, "first" and "last", when I would rather be referencing an object with both properties inside.

How do I achieve this?


Solution

  • If the modelValue is changed to an object, then the inputs will need to emit an updated object.

    App.vue

    <template>
      <h1>{{ firstLast.first }} {{ firstLast.last }}</h1>
      <UserName v-model="firstLast" />
    </template>
    

    UserName.vue

    <script>
    export default {
      props: {
        modelValue: Object
      },
      emits: ['update:modelValue']
    }
    </script>
    
    <template>
      <input
        type="text"
        :value="modelValue.first"
        @input="$emit('update:modelValue', { ...modelValue, first: $event.target.value })"
      />
      <input
        type="text"
        :value="modelValue.last"
        @input="$emit('update:modelValue', { ...modelValue, last: $event.target.value })"
      />
    </template>
    

    This becomes trivial if you use the newer Composition API and <script setup> which gives access to defineModel macro

    Using Composition API

    UserName.vue

    <script setup>
    const firstLast = defineModel()
    </script>
    
    <template>
      <input
        type="text"
        v-model="firstLast.first"
      />
      <input
        type="text"
        v-model="firstLast.last"
      />
    </template>