vue |tip

V-model support without requiring value prop

elisiondan

Daniel Charvát

Posted on January 22, 2021

V-model support without requiring value prop

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">
Enter fullscreen mode Exit fullscreen mode

turns into

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>
Enter fullscreen mode Exit fullscreen mode

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: "",
    },
  },
Enter fullscreen mode Exit fullscreen mode

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
      );
    },
  },
Enter fullscreen mode Exit fullscreen mode

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" />
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
elisiondan
Daniel Charvát

Posted on January 22, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related