Multiple v-model for the rest of us
Published: 2022-02-06 19:05:15

Vue 3 has been around for a minute and one of my favourite features has been the addition of multiple v-model bindings for custom components.

See here on how to use new feature.
https://dev.to/snehalk/multiple-v-model-in-vue-3-31lc


In summary - with multiple v-models, you can create custom components like this.

```html
<CustomMultipleInput 
  v-model:title="heading"
  v-model:subHeading="pageSubHeading"
/>
```

The benefit here is that you can abstract common but complex components and bind to the specific parts you're interested in.

------------------------------------------

That's nice and all. But what about Vue 2?
Not everyone can easily bump up or upgrade to Vue 3.

A classic yet simple example I always run into is in building a basic `SelectInput` component that makes very little assumptions and allow you bind any kind of select options and text input.

Lets take a look.

The SelectInput component at its barest looks like this.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rq3du8c0r4wqfkid8ppq.png)

 
it's simply a select and input element mash up.

The code for the above image could look something like this.

`SelectInput.vue`
```vue
<template>
  <div class="select-input">
    <select class="select-input__select">
      <slot />
    </select>
    <input class="select-input__input" />
  </div>
</template>

<style>
.select-input {
  border: solid 1px #e2e2e2;
  max-width: 250px;
  font-size: 14px;
  padding: 5px 6px;
}

.select-input__select {
  border: none;
  background: none;
  max-width: 69px;
}

.select-input__input {
  border: none;
  width: 180px;
  border: none;
  outline: none;
}
</style>
```

Not the most complex component. But helps illustrate a real world use case.

---------------------------------------


## Emitting Objects

A popular approach when dealing with complex components in vue 2 is to emit javascript objects that contain all the fields you want to bind to.

So something like this.

`SelectInput.vue`
```vue
<template>
  <div class="select-input">
    <select v-model="value.select" @change="emit" class="select-input__select">
      <slot />
    </select>
    <input v-model="value.input" @input="emit" class="select-input__input" />
  </div>
</template>

<script>
export default {
  model: {
    prop: "value",
    event: "change"
  },

  props: {
    value: {
      type: Object,
      default: () => ({
        select: undefined,
        input: undefined,
      }),
    },
  },

  methods: {
    emit() {
      this.$emit('change', this.value);
    }
  },
};
</script>
```

Here, we're taking advantage of the fact that Vue allows direct mutation of prop fields of objects for a quick 2-way binding component. 
Simple and easy (Don't use this code in production 🌚).

The custom component can now be used as below. 
Changes made within the component will reflect in App.vue and changes made in App.vue will reflect in the component.

`App.vue`
```vue
<template>
  <SelectInput v-model="amount">
    <option value="NGN">NGN</option>
    <option value="GHS">GHS</option>
    <option value="USD">USD</option>
  </SelectInput>
</template>

<script>
export default {
  data () {
    return {
      amount: {
        select: "NGN",
        input: 0,
      }
    }
  }
}
</script>
```

This works and helps you abstract complex components, forms etc and emit only the parts you need as fields in the object.

A downside of this approach is that it can be very brittle.
The `SelectInput` component has to dictate the structure of the `amount` object. If we mutate it in a way the `SelectInput` component cannot accomodate, we can easily break things.

This means we must keep track and maintain that object structure and as a consequence, the code can be less readable (especially with larger objects).


-------------------------------------------


# Vue2 Multiple V-Models

A solution!!! 🕺🏻

I wrote this library to solve these problems. https://github.com/michaelolof/vue2-multiple-vmodels

First lets install!


```bash
$ npm install --save vue2-multiple-vmodels
```

```js
import Vue from "vue";
import Vue2MultipleVModels from "vue2-multiple-vmodels";

Vue.use(Vue2MultipleVModels);
```
The library comes with 2 helpful vue directives.

1. `v-model-destruct`
2. `v-models`


-------------------------------------------------------------


## Destructuring v-model objects with v-model-destruct

Consider the earlier example where we emited objects. 
What if the object was a lot more complex but you were only interested in one field in the object? 
What if in the `SelectInput` component, You were only interested in the amount field?

> With `v-model-destruct` you can destruct an incoming v-model object and bind only to the fields you're interested in.

So in `App.vue` we could write...
```vue
<template>
  <SelectInput v-model-destruct:input="amount">
    <option value="NGN">NGN</option>
    <option value="GHS">GHS</option>
    <option value="USD">USD</option>
  </SelectInput>
</template>

<script>
export default {
  data () {
    return {
      amount: 300,
    }
  }
}
</script>
```

In the above snippet, we are destructing the v-model object and binding (2-way binding), only the input field of the v-model object to the `amount` variable.

And just in the same way we could specifially bind the `select` field to the `currency` variable E.g
```vue
<template>
  <SelectInput v-model-destruct:select="currency" v-model-destruct:input="amount">
    <option value="NGN">NGN</option>
    <option value="GHS">GHS</option>
    <option value="USD">USD</option>
  </SelectInput>
</template>

<script>
export default {
  data () {
    return {
      currency: "GHS",
      amount: 300,
    }
  }
}
</script>
```


--------------------------------------------------------

## Multiple v-model with `v-models`

The earlier example with `v-model-destruct` is great for already existing components designed to emit objects. But it isn't really a _multiple `v-model` alternative_.

>The `v-models` directive simply put is my Vue2 response to the new multiple `v-model` feature in Vue 3.

Lets see how it works.

Using the same `SelectInput.vue` component we can design our component to support `v-models` like this...

`SelectInput.vue`
```vue
<template>
  <div class="select-input">
    <select v-model="select" class="select-input__select">
      <slot />
    </select>
    <input v-model="input" class="select-input__input" />
  </div>
</template>

<script>
export default {
  models: [
    { data: "select", event: "models:select" },
    { data: "input", event: "models:input" },
  ],
  data() {
    return {
      select: undefined,
      input: "",
    }
  },
  watch: {
    select(newVal) {
      this.$emit("models:select", newVal);
    },
    input(newVal) {
      this.$emit("models:input", newVal);
    },
  },
}
</script>
```

The structure of this component is quite different from the earlier example - but its very straight forward.

-  We define our select and input elements and bind a data field to them respectively.
-  Then we say - anytime the data field changes we want to emit a `models:<xxx>` event. Prefixing the emitted event with a `models:` is required for this feature to work. The directive checks for it.
- We then register the data field and the emitted event in the `models` section.

Thats it!!

This is what the `App.vue` component will look like utilizing this directive.

`App.vue`
```vue
<template>
  <SelectInput v-models:select="currency" v-models:input="amount">
    <option value="NGN">NGN</option>
    <option value="GHS">GHS</option>
    <option value="USD">USD</option>
  </SelectInput>
</template>

<script>
export default {
  data () {
    return {
      currency: "GHS",
      amount: 300,
    }
  }
}
</script>
```

As you might have noticed, we didn't define any props in the component nor did we pass any to the component.
The **`v-models` directive doesn't need props to work**. You could still define your prop and update the data field whenever the prop changes - but `v-models` doesn't rely on props. 
It relies on data. This is because _props are immutatble_.

We also have a `models` array section instead of `model` for normal v-model. Here you define the data and corresponding event for all the fields you want v-models to work on. 
As mentioned earlier, the event must be preceeded with `models:` for the directive to work.

That's it.
Not too shabby yeah? 😂


------------------------------------------------

And that's how you do multiple v-model in Vue 2.
Thanks for reading!! 😉