VUE v-model, two-way data binding and editing in multi nested components, dynamic components
Tags: vue, components, binding, dynamic
Published: 2020-01-05 20:10:18

Hello guys!

Check out my first public VUE sandbox. Check it on desktop. It shows how to deal with multi nested components in VUE and how to pass data with v-model from the topmost component down to the children components.

I am quite new to VUE, so I really just want to share my experiences and not to tell you, how do you *must* do it. Maybe some more experienced VUEers will highlight the weak spots in the comment section. They are welcome!

So let's move on! At first, this is not a complete beginner article, you need to be familiar with the basics of VUE. The sandbox shows, how to pass data between components just using `v-model` and without `emit`, or `get`, `set`. There is a `value-peeker` object, which real time shows the current `value` for the given component. On the right you can see the root object, `App.inputList`.

Here is the sandbox:

{% codesandbox trusting-pare-lmf69 %}

##Some intro to this sandbox

Every article I read about VUE's two-way binding in nested components was showing examples only with two level deep nesting and used one of these techniques to ensure two-way binding:

The first one is creating computed getters and setters for the property:

```html
<template>
  // use the value
</template>
```

```javascript
props: {
  value: Object
},
computed: {
  value: {
    get () {
      return this.value
    },
    set (val) {
      this.$emit('input', val)
    }
  }
}
```

or this, the second technique relies on a watcher:

```html
<template>
  // use the localValue
</template>
```

```javascript
props: {
  value: Object
},
data () {
  localValue: {...this.value}
},
watch: {
  localValue(val) {
    this.$emit('input', val)
  }
}
```

IMHO, nobody ever mentioned, that in multi nested components we do not have to use any of those techniques to pass the object. The only thing we need is `v-model`.

###App.vue

I created the root or base object `inputList`, which will be edited by multiple nested components:

```javascript
  data() {
    return {
       inputList: [
        {
          name: "titles",
          label: "Titles",
          type: "my-input-list",
          items: [
            { label: "PhDr", value: "phdr" },
            { label: "Dr", value: "dr" }
          ]
        },

        {
          name: "firstname",
          label: "First name",
          type: "my-input-text"
        },
...

```

The root object is passed via `v-model` to a custom component called `my-form`:

```html
<template>
  <div id="app">
    <div class="col1">
      <my-form v-model="inputList"/>
    </div>
...
```

###Components/Form.vue###

Iterates the object `value`, which is a vue *property* and was passed by *App.vue* using the `v-model` directive. For each entry it creates a custom component. The component is dynamically created using the vue `component` tag. You have to set it's property called `is` to the name of the component, you want to create.

```html
<template>
  <div>
    <div v-for="(item, idx) in value" :key="idx">
      <component :is="item.type" v-model="value[idx]"></component>
      <div class="hr"></div>
    </div>

    <!-- Save button -->
    <input type="button" value="SAVE" @click="save" class="save">
  </div>
</template>
```

You need to register (or load dynamically, it is another story) your components, before you can create them with the `component` tag.

Javascript imports

```javascript
import InputText from "./dynamic/InputText";
import InputBoolean from "./dynamic/InputBoolean";
import InputList from "./dynamic/InputList";

...
```

Registering VUE components:

```javascript
export default {
  name: "Form",

...

  components: {
    "my-input-text": InputText,
    "my-input-boolean": InputBoolean,
    "my-input-list": InputList
  }
};
```

**Component with dynamic** `v-model`

Maybe you are surprised why I use `v-model="value[idx]"`

```html
<div v-for="(item, idx) in value" :key="idx">
  <component :is="item.type" v-model="value[idx]"></component>
  <div class="hr"></div>
</div>
```

instead of `v-model="item"`

```html
<div v-for="(item, idx) in value" :key="idx">
  <component :is="item.type" v-model="item"></component>
  <div class="hr"></div>
</div>
```

You can't use the iteration variable `item` as `v-model` because of variable scope, so just stick to the first valid solution.

###components/dynamic/InputList.vue###

```html
<template>
  <div>
    <h1>{{ value.label }}</h1>

    <my-value-peeker v-model="getPeekValue" :label="value.type + '.value:'"/>
    
    <div class="component">
      <select v-model="value.value" multiple>
        <option v-for="(item, idx) in value.items" :key="idx" :value="item.value">{{ item.label }}</option>
      </select>

      <p>
        <input type="button" value="CREATE NEW ITEM" @click="createNewItem" v-if="!isCreateNewItem">

        <my-input-list-item-editor
          v-model="newItem"
          v-if="isCreateNewItem"
          @confirm="confirmAdd"
          @cancel="cancelAdd"
        />
      </p>
    </div>
  </div>
</template>
```

Here we have our peeker component with a dynamic `:label`. Sometimes it makes sence to do it this way, however it would be cleaner to have a *computed* `label` *property* in `<my-value-peeker>` which alters the label.

```html
<template>
  <div>
    <h1>{{ value.label }}</h1>

    <my-value-peeker v-model="getPeekValue" :label="value.type + '.value:'"/>   
...
```

Set the `v-model` attribute for the html `select` tag

```html
      <select v-model="value.value" multiple>
        <option 
          v-for="(item, idx) in value.items" 
          :key="idx" 
          :value="item.value"
         >{{ item.label }}</option>
      </select>
...
```

We have a `my-input-list-item-editor`, which allows us to add new records to the collection contained in `items`. This component `emits` two custom events called `confirm` and `cancel`. The names are self explaining.

```html
        <my-input-list-item-editor
          v-model="newItem"
          v-if="isAdd"
          @confirm="confirmAdd"
          @cancel="cancelAdd"
        />
```


```javascript
 confirmAdd() {
      // hide add UI
      this.isAdd = false;

      // push a shallow copy
      this.value.items.push({ ...this.newItem });
    },
```

The method `confirmAdd` just adds a shallow copy of `newItem` to the collection of `items` on the components `value` object. Since it is an `Array`, we can use *push*. Create a shallow copy (deep clone is not needed) of the object prior to adding it to the collection. If your `newItem` contains objects references, you would end up with creating a deep clone of your object instead of the shallow copy. 

Use 

```javascript
const arrayDeepClone = JSON.parse(JSON.stringify(originalArray));
```

or

```javascript
const arrayDeepClone  = Vue._.cloneDeep(originalArray);
```

For the second one you need to install [vue-lodash](https://www.npmjs.com/package/vue-lodash) and set it up. I just ended up adding the initialization directly to *main.js*. If you are using a framework which hides main.js from you, you have to initialize it other way. For example in Quasar you end up with adding a new boot component.

```javascript
import VueLodash from "vue-lodash";
Vue.use(VueLodash);
```

And the event 'cancel' will be handled by method `cancelAdd`. It just hides the add new item UI.

```javascript

    cancelAdd() {
      // hide add UI
      this.isAdd = false;
    }
```

###components/dynamic/InputText.vue

This is pretty basic, just pass the `v-model` to the `input`.

```html
<template>
  <div>
    <my-value-peeker v-model="getPeekValue" :label="value.type"/>
    <div class="component">
      <label>{{ value.label }}:</label>
      <input type="text" v-model="value.value">
    </div>
  </div>
</template>
```

Just the basic stuff.

> It is always a good idea to use this style of property definition and set the property type, if it is required, and if not, you should return a default value. 

```javascript
export default {
  props: {
    // v-model injects the object to be edited into props.value
    value: {
      type: Object,
      required: true
    },    
  },
  methods: {
    // just the value for the peeker
    getPeekValue() {
      return this.value;
    }
  }
};
```

As you can see no `emit` here, no local copies, no setters, no getters, just the `v-model` directive. We are actually ended up editing a `string` property called `value` on object `value`. This way we are editing directly the objects properties, but we do not touch the object reference itself.

###components/dynamic/InputBoolean.vue

The same basic thing, as in `InputText`, just the input was changed to a checkbox.

```html
<template>
  <div>
    <my-value-peeker v-model="getPeekValue" label="InputBoolean"/>
    <div class="component">
      <label>{{ value.label }}</label>
      <input v-model="value.value" type="checkbox" label="YES">
    </div>
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: Object,
      required: true
    }
  },
  methods: {
    getPeekValue() {
      return this.value;
    }
  }
};
</script>
```

###components/InputListItemEditor.vue

We have one `my-value-peeker` object here and two html `input` tags, bound to `value.label` and `value.value` using `v-model`.

```html
<template>
  <div>
    <my-value-peeker v-model="getPeekValue" label="InputText"/>
    <div class="component">
      <p>
        <label>Label:</label>
        <input type="text" v-model="value.label">
      </p>
      <p>
        <label>Name:</label>
        <input type="text" v-model="value.value">
      </p>
      <input type="button" value="Confirm" @click="confirm">
      <input type="button" value="Cancel" @click="cancel">
    </div>
  </div>
</template>
```

As far as we want to have control over when the `value`, which gets written to the parent component, we use two buttons. Both of them has `click` event listeners set.

```javascript
confirm() {
  this.$emit("confirm", this.value);
},
```

This sends or emits a message to the parent component, in our case to `components/dynamic/InputList.vue`. Our parent component handles these messages as written above. In `confirm` method we send a reference to the object to the parent component.

> You can send more parameters with `$emit`. If two or more parameters are used, send the payload as `object`, like this: this.$emit("confirm", { value: this.value, entity: "users", counter: 0, immediate: false})

> This way you are building your object by setting property names in `v-model`. Beware of typos, you can easily end up with objects with mispelled property names.

###Bonus

For demonstration purposes I added a button to *App.vue* which modifies the `inputList[0]` and adds a new item to it. You can see how the change is immediatelly propagated to all children components.

```javascript
    addTitle() {
      this.inputList[0].items.push({
        label: "Kokki-" + new Date().getTime(),
        value: "kokki-" + new Date().getTime()
      });
    }
```

And one more: If you press the *CREATE NEW ITEM* in a `ListItem` component a counters starts to count presses. It is just for demonstration, how can we modify the base object from the child component.

##Conclusion

`v-model` is cool!

###Hopefully someone will find this article helpfull! Let me know in the comments!

##Feel free to play on my sandbox, comments/critics are appreciated.

Keep coding!