Implementing Two-Way Computed Properties In Vue.js

Implementing Two-Way Computed Properties In Vue.js

A guide on how to use model directives with two-way computed properties in Vue.js

Introduction

If you are a Vue.js developer like me, you must have heard about the very interesting v-model directive which is gaining popularity in the Vue.js world.

The v-model directive is a very useful and clean approach for binding any changes to a value as well as listening to the changes that may affect that value. In technical terms, you can say that the v-model is a powerful directive provided by Vue.js to add two-way data binding in vanilla Vue.js.

Implementing the v-model directive with a computed property

Before starting with the implementation of the V-model, we have to understand the under-hood working of the V-model directive.

  • While v-model=”dataProperty”seems like magic at first glance, it’s actually a shorthand for the<input :value="dataProperty" @input=dataProperty = $event.target.value" />
  • By default, the v-model is assessed every time the input event is fired (i.e. on key-down or paste). If you want to wait until the user has finished typing and blurred the input field (click outside of the input field), you can use the lazy modifier over the v-model. Use the code given below to proceed:
<input v-model.lazy="value" />
  • In the CustomSearchInput.vue file, create a custom component that accept props like value, optionsArr and placeholderValue.
  • Here is the template structure for the CustomSearchInput component :
<template>
  <div class="container">
    <input ref="inputField" v-model="inputVal" class="val-input" type="text" :placeholder="placeholderValue />

    <!-- options Dropdown -->

    <div class="data-options" v-if="filterOptionArray.length > 0" :class="{ 'd-none': openFlag }" >
      <div ref="dataDiv" class="general-modal-scroll-bar">
        <li
          v-for="option in filterOptionArray"
          :key="option"
          class="item"
          @click=" inputVal = option;
            $emit('input', option); " >
          {{ option }}
        </li>
      </div>
    </div>
  </div>
</template>
  • Now, let’s declare props and reactive data property using the code snippet given below:
<script>
export default {
  name: "SearchInput",
  props: {
    optionsArr: {
      type: Array,
      default: () => [],
    },
    value: {
      type: String,
      default: "",
    },
    placeholderValue: {
      type: String,
      default: "",
    },
  },
  data() {
    return {
      inputVal: "",
      openFlag: true,
    };
  },
  ...
  ...
</script >
  • Now we will move on to write the logic for the drop-down toggling which includes the computed property, and if the user clicks any option we have to close the drop-down. We will have to close the dropdown when the page loads or the user leaves the page. For this, we are using Vue.js life cycle hooks, mounted and beforeDestroy. Use the code snippet given below to proceed:

    <script>
    ...
    ...
    mounted() {
      this.inputVal = this.value;
      // Closing the options panel on load of the component
      document.addEventListener("click", this.close);
    },
    beforeDestroy() {
      // Closing the options panel on leaving the component
      document.removeEventListener("click", this.close);
    },
    ...
    ...
    
  • Here is where the main logic comes in! We have to filter the array coming from prop depending upon the user's input. Run the following code snippet:

<script>
  ...
  ...
  // this computed property is responsible for the fitering the array according input value
  computed: {
    filterOptionArray() {
      if (this.inputVal.length === 0) {
        return this.optionsArr;
      } else {
        return this.optionsArr.filter((option) => {
          let temp = option.toLowerCase();
          return temp.match(this.inputVal);
        });
      }
    },
  },
  // keep a watch on inputVal if it's value change then we close the options Panel
  watch: {
    inputVal(newVal) {
      this.openFlag = false;
    },
  },
  ...
  ...
</script>
  • We also have to close the dropdown if the user clicks outside the drop-down area which can be done by running the following code:

    <script>
    ...
    ...
    methods: {
      // Options Panel close method
      close(e) {
        if (
          e.target !== this.$refs.dataDiv &&
          e.target !== this.$refs.inputField
        ) {
          this.openFlag = true;
        }
      },
    },
    };
    </script>
    
  • On following the aforementioned steps, we will receive the value as a prop from the parent component. As per the Vue 2 design pattern, we were not allowed to manipulate the prop data directly. To resolve this, we will be creating a local data property called inputVal and move on to storing the value props data in inputVal, and bind the inputVal to the input field.

  • Now, we will be creating a computed property called the filterOptionArray, which is responsible for the options available to select from the dropdown depending upon the user input. This computed property returns an array, which we use to display the drop-down list.
  • When a user clicks on any drop-down list item, it emits an event to inform its parent about the changes. Now the template for the parent component will be the App.js file. Run the following code:
<template>
  <div id="app">
    <CustomSearchInput
      :optionsArr="arr"
      v-model="selected"
      placeholderValue="Enter start typing to get options"
    />

    <div class="display-data" v-if="selected.length > 0" >Selected Value: {{ selected }}</div>
  </div>
</template>
  • Now, we have to import the child component and declare the required data values as shown below:
<script>
import CustomSearchInput from "./components/CoustomSearchInput";
export default {
  name: "App",
  components: { CustomSearchInput },
  data() {
    return {
      arr: ["jhon", "jay", "shiv", "jani", "shawn", "shelly", "bob", "carry", "Alex", "Anthoney" ],
      selected: "",
    };
  },
};
</script>

Play with the code here

  • We can use this method to combine computed properties and the v-model in order to write the most robust code on Vue.js.

Add-on Notes:

  • If you’d like to cast user input to a number instead of a string, add the v-model.number modifier.
  • The v-model.trim modifier will strip leading or trailing whitespace from the bound string. (It can not be used in conjunction with the v-model.number).

Conclusion

The combination of the v-model and computed property makes your code robust and more reusable. Hope this article helps you to understand how to use two-way data binding in Vue.js by using the v-model directive and computed properties.