Radical_Activity
Radical_Activity

Reputation: 2738

How to use v-model and computed properties on Input fields?

I have a simple component with 2 input fields:

<template>
    <div>
        <input type="text" v-model="name">
        <input type="text" v-model="alias">
    </div>
</template>

<script>
    export default {
        data() {
            return {
                name: "",
                alias: ""
            }
        }
    }
</script>

I want to automatically insert the name model's value to the alias field IF the alias field is empty. If it's not empty, I want it to use its own value.

Here is my attempt:

<template>
    <div>
        <input type="text" v-model="name">
        <input type="text" v-model="alias">
    </div>
</template>

<script>
    export default {
        data() {
            return {
                name: "",
                alias: ""
            }
        },
        computed: {
            alias: {
                get: function() {
                    if (this.alias.length < 1) {
                        return this.name
                    } else {
                        return this.alias
                    }
                },
                set: function(newValue) {
                    this.alias = newValue
                }
            }
        }
    }
</script>

The problem is that alias doesn't get the actual value in the alias property if I type something into the name field. I can only see it visually - but it doesn't hold the value. However, if I type into the alias field, it gets updated properly.

What am I missing here and how can I make it the way I want it?

Upvotes: 0

Views: 2670

Answers (2)

Joe Dalton
Joe Dalton

Reputation: 346

Firstly, you cannot have a computed property and a data property with the same name. Since both computed and data properties end up as properties on the same state object, one will overwrite the other.

Secondly, and I think you did this because of the first point, in your computed alias getter, your reference the alias again, which is essentially referencing itself, and looks like it could give some inconsistent return values.

I can think of two solutions to your issue:

1) Use a watcher on name:

Create a watcher function for name, and in it set this.alias to the same value as name when alias is blank, or if it's the same as the previous name value.

<script>
    export default {
        data: () => ({
            name: "",
            alias: ""
        }),
        watch: {
            name(newVal, oldVal) {
                if (!this.alias || this.alias === oldVal) {
                    this.alias = newVal;
                }
            }
        }
    }
</script>

2) Use explicit :value and @change/@keyup bindings on the name input:

v-model is a convenience method that sets both of these for you, but in your case you want to do some more logic in the change handler that just setting a state property value.

<template>
    <div>
        <input
          type="text"
          :value="name"
          @keyup="onNameInput"
        />
        <input type="text" v-model="alias">
    </div>
</template>

<script>
    export default {
        data: () => ({
            name: "",
            alias: ""
        }),
        methods: {
            // Check and set both values on name input events
            onNameInput(e) {
                if (!this.alias || this.alias === this.name) {
                    this.alias = e.target.value;
                }
                this.name = e.target.value;
            }
        }
    }
</script>

Upvotes: 1

Lawrence Cherone
Lawrence Cherone

Reputation: 46602

Computed won't work because it should be treated as immutable.

Also because the model will be updated on each input, a watch won't work either, it would only pick up the first char of what you enter, unless its pre-populated.

This is how I would do it, simply add a @blur event on the name input then fire a method which populates alias if it's empty and on alias in case they empty it out.

The same method could be used in mounted, if you pre-populate the models, or you could watch it.

{
  template: `
  <div>
     <input type="text" v-model="name" @blur="setAlias()">
      <input type="text" v-model="alias" @blur="setAlias()">
  </div>
  `,
  data() {
    return {
      name: '',
      alias: ''
    }
  },
  methods: {
    setAlias() {
      if (this.alias === '') {
        this.alias = this.name
      }
    }
  }
}

Upvotes: 1

Related Questions