The behavior of Two way binding in Vue.js

In the ever-evolving landscape of web development, Two-way binding stands out as a crucial and transformative feature that significantly enhances the efficiency and simplicity of creating dynamic web applications. At the heart of this innovation lies the v-model directive, a powerful tool that streamlines the development process by establishing a seamless connection between the user interface and the underlying data model

Difficulty Level: Advanced

Prerequisite: You should be familiar with Vue.js 2.x / 3.x and familiar with the usage of v-model directive.

Takeaway: This article gives you an understanding of the behavior of v-model in custom component implementation in Vue 2.x and 3.x

Two-way binding schema

What is Two-way binding?

  1. 1. When the data model properties get updated, the UI elements getupdated.
  2. 2. When UI elements get updated, the same changes are propagated back to the data model property almost immediately.

V-model directive is commonly used in form elements like input, select, text area, and radio in Vue.

1
2
3
4
5
6
7
    <!-- Manually bind the value and update the dta property on change event -->
    <input
      :value="messageInput"
      @input="event => messageInput = event.target.value">

    <!-- The v-model directive simplifies the above -->
    <input v-model="messageInput">

We can also utilize the advantage of v-model in custom component to build two way communication between parent and child component. The behavior and implementation is slightly different in vue 2.x and 3.x

Here we can see the differences with examples

Vue 2.x

Parent Component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
  <main>
    <WelcomeToVue v-model="greetingMessage" />
  </main>
</template>

<script>
  import WelcomeToVue from '@/components/WelcomeToVue.vue'

  export default {
    name: 'HomeView',
    components: {
      WelcomeToVue
    },
    data () {
      return {
        greetingMessage: ''
      }
    },
    watch: {
      greetingMessage (newValue) {
        console.log(newValue)
      }
    }
  }
</script>

Child Component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
  <div>
    <button @click="userClicked">Click Here</button>
  </div>
</template>

<script>
export default {
  name: 'WelcomeToVue',
  props: {
    value: {
      type: String,
      default: ''
    }
  },
  methods: {
    userClicked () {
      this.$emit('update:value', 'message updated')
    }
  }
}
</script>

The data sync is achieved by emitting the event update:value , the shorthand for v-model is

1
<WelcomeToVue :value="greetingMessage" @input="greetingMessage = $event" />

If we wanted to change prop or event names to something different, we would need to add a model option to the child component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!-- Parent Component -->

<WelcomeToVue v-model="greetingMessage" />

export default {
  model: {
    prop: 'greetingMessage',
    event: 'change'
  },
  props: {
    // this allows using the `value` prop for a different purpose
    value: String,
    // use `greetingMessage` as the prop which take the place of `value`
    title: {
      greetingMessage: String,
      default: 'Default message'
    }
  }
}

So, v-model in this case would be a shorthand to:

1
<WelcomeToVue :title="greetingMessage" @change="greetingMessage = $event" />

Vue 3.x

In Vue 3.x, it has breaking changes during migration from 2.x to 3.x
Prop: value -> modelValue
Event: update:value -> update:modelValue
In 3.x we can have multiple v-model bindings in same component.

Parent Component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
  <main>
    <WelcomeToVue v-model="greetingMessage" />
  </main>
</template>

<script>
  import WelcomeToVue from '@/components/WelcomeToVue.vue'

  export default {
    name: 'HomeView',
    components: {
      WelcomeToVue
    },
    data () {
      return {
        greetingMessage: ''
      }
    },
    watch: {
      greetingMessage (newValue) {
        console.log(newValue)
      }
    }
  }
</script>

Child Component

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
  <div>
    <button @click="userClicked">Click Here</button>
  </div>
</template>

<script>
export default {
  name: 'WelcomeToVue',
  props: {
    modelValue: {
      type: String,
      default: ''
    }
  },
  methods: {
    userClicked () {
      this.$emit('update:modelValue', 'message updated')
    }
  }
}
</script>

You need a help building a web application? Drop us a line here.

Written by:

 avatar

Chandra Sekar

Senior Frontend Developer

Chandra is an exceptional Vue.js frontend developer who consistently delivers outstanding results. With a keen eye for detail and a deep understanding of Vue.js, Chandra transforms design concepts into seamless and visually captivating user interfaces.

Read more like this: