Daniel Charvát
Posted on January 22, 2021
Have you ever wished to create a component that supports v-model
directive, but works without it as well? First things first. If you've tried Vue.js you've probably learned that you can bind variables on inputs. This creates a two-way data binding which syncs the variable and the input's state. All you need to do is to use the v-model directive.
You may also have learned that you can use this directive with any custom component since v-model
is just a syntax sugar to cover both ways of the data binding. You can learn more about this here. Hence
<input v-model="searchText">
turns into
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
As you can see, in order to implement the support, you have to declare a prop variable called value and emit an event labeled input. And that's it.
However, you will quickly find out that at this point the component indeed supports the v-model
directive, but it doesn't work at all without it. That's often undesirable. For instance, imagine you'd like to create a custom search component that includes a text input. Since it's a mere extension of a text input, it's reasonable that it should support v-model
. But it is also reasonable that you'd like to be able to use it without it since the input inside would normally work straight away had it been a plain HTML element. Let's tackle this.
Optional v-model support
Let's start by creating a simple search component that will accept value
as prop. If the user doesn't provide it, it's initiated to an empty value.
props: {
value: {
type: String,
default: "",
},
},
However, we can't use this prop directly in the input since that would mutate it which is not recommended. To circumvent this problem we'll create a clever computed value that will use the value prop if passed from the parent, or a custom local value otherwise. We'll make use of the extended computed property syntax where one can declare different functions for setter and getter of the computed function.
data() {
return {
localValue: this.value,
};
},
computed: {
searchValue: {
get() {
return this.isValuePropSet() ? this.value : this.localValue;
},
set(value) {
this.$emit("input", value);
this.localValue = value;
},
},
},
methods: {
isValuePropSet() {
return (
!!this.$options.propsData && this.$options.propsData.value !== undefined
);
},
},
Let's first take a look at the getter. When retrieving the value, the isValuePropSet()
method is invoked. This method returns true when the value
prop was set by the parent, not initialized to empty string by the default property. So when it was set from the outside, we'll just return the value property and the component works as if it was implemented as a regular component with v-model
support. However, when the value was not set, then the getter returns localValue
instead. In the setter the current value is both emitted as an input event and stored in the localValue
.
With this pattern, we can bind the clever searchValue
computed property to the input as usual
<input v-model="searchValue" />
And that's it. The search component works with v-model
attached as well as without it. Check out the example sandbox to see it wholly in action.
Posted on January 22, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.