We're live-coding on Twitch! Join us! FREE Webinar: Should I use React or Vue?
Add `v-model` Support to Custom Vue.js Component

Add `v-model` Support to Custom Vue.js Component

v-model is one of the few directives that comes bundled with Vue.js. The thing that makes it great is the fact that it allows for two-way data binding between our data and views.

With two-way data binding, when we update our data via input fields or whatever, we can modify the DOM without having to do DOM work.

By the end of this article, you will come to discover that the v-model directive is only syntactic sugar that helps improve how we manage data in Vue.js. You'll also see how to use it for your own components.

How v-model works internally

From our knowledge of HTML, we know that input, select, textarea are the main ways we feed data to our application.

So, say we have:

<input v-model="email" />

v-model just translates to:

<input :value="email" @input="e => email = e.target.value" />

What this means is that for v-model to work, it expects the element or component in question to receive a prop (default is value) and also emit an event (default is input)

Depending on the element — Vue decides how to listen and handle the data. For textarea, select and some input types — Vue uses the expansion above to handle them.

For radios and checkboxes — Vue uses their checked prop and listens for their change event.

React LogoReact Logo
Upgrade Your JS
Go from vanilla JavaScript 👉 React

For elements like select tags and checkboxes that can accept multiple values, Vue will automatically return an array of selected values.

How to add v-model to custom components

To let our component support v-model two-way binding, I stated earlier that the component needs to accept a value prop and emit an input event.

Let's create a sample component called basic-input. We'll be using Vue's single file component:

<template>
  <input @input="handleInput" />
</template>

<script>
export default {
  prop: ['value'],
  data () {
    return {
      content: this.value
    }
  },
  methods: {
    handleInput (e) {
      this.$emit('input', this.content)
    }
  }
}
</script>

As you can see from the example above, all the component is doing to support v-model is it accepts a value prop and emit an input event.

We'll use the above component as we normally do and just add the v-model directive. Like this:

<basic-input v-model="email" />

So, see how easy it is to add two-way data binding to our Vue component. Just like everything with Vue — quite easy.

Customizing v-model prop and event

Let's take it a step further. We might not want to use the default event and prop needed to add v-model support to our components. Thankfully, Vue allows us to customize it.

To customize the event and prop, we just add a model property to our component and define the new values like this:

export default {
  prop: ['hidden'],
  model: {
      prop: 'hidden',
      event: 'blur'
  }
  methods: {
      handleInput (value) {
          this.$emit('blur', value)
      }
  }
}

After defining the component above, when Vue meets

<basic-input v-model="email" />

Vue will automatically convert it into:

<basic-input :hidden="email" @blur="e => email = e.target.value" />

With this in place, we can avoid conflict when defining our component's prop and events.

Using v-model on ContentEditable

Content editable elements are HTML elements that cannot take input but redefined to do so... Yikes!

Basically, a content editable is a div or a similar element that can be configured to work as an input.

If it's still not clear, you can head over to _[MDN (Mozilla Developer Network)](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editablecontent), they have a good piece there.

We define content editable elements by adding the contenteditable attribute to the element, in this case, a div.

<div class="editor" contenteditable="contenteditable"></div>

We mostly use content editable elements for WYSIWYG editors as they are easier to work with and is supported by a large amount of browsers.

v-model will work on content editable elements, but, we need to explicitly use the content of the element otherwise the content will not be emitted.

What I mean is that defining our component like this won't suffice.

<template>
  <div contenteditable="contenteditable" @input="updateInput">
      {{ content }}
  </div>
</template>

<script>
  export default {
      prop: ['value'],
      data () {
          return { content: '' }
      },
      methods: {
          updateInput () {
              this.$emit('input', this.content)
          }
      }
  }
</script>

If you try to use the component above — v-model won't be updated even though we have all the requirements in place. I don't really know why this doesn't work, I think it has to do the fact that the template is over-written. Anyway, the solution is simple — we need to emit the content of the div instead.

To emit, we need to grab the innerText or innerHTML of the div. So, our updateInput method needs to look like this

updateInput () {
  this.$emit('input', this.$el.innerText)
}

We can also use the content of a _ref instead of the root element's content._

With this in place, v-model will work for content editable elements. We could also update this.content in the updateInput method.

Conclusion

Glad you made it here. Now that you have seen how to use v-model with custom Vue components, go build or refactor your components that require the use of v-model.

Do share with your friends and let us know your thoughts in the comments.

Like this article? Follow @KayandraJT on Twitter