43

Please refer to my snippet below. How can I get the old value of the changed model, in this case is the age in vuejs?

var app = new Vue({
  el:"#table1",
  data:{
   items:[{name:'long name One',age:21},{name:'long name Two',age:22}]
  },
  methods:{
    changeAge:function(item,event){
      alert(item.age);
    }
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>

<table class="table table-cart" id="table1">
   <thead>
     <tr>
       <th>Name</th>
       <th>Age</th>
     </tr>
  </thead>
   <tbody>
     <tr v-for="(item,index) in items">
       <td>{{item.name}} :</td>       
       <td>
         <input type="text" name="qty"
                v-model="item.age"   
                @change="changeAge(item,$event)">
       </td>
     </tr>
   </tbody>
 </table>

I am trying to use watch, but I am not able to find any help in watching the age an array of items.

1
  • 4
    your changeAge(item,$event) actually helped me with a different problem. Thanks for the sharing.
    – tazboy
    Commented Apr 4, 2018 at 5:09

8 Answers 8

39

This solution assumes that you need the old value only when handling the change coming from user input - which is probably the case, and not a generic model change. Below I added a sketch of solution watching all changes in if it's necessary, though perhaps it's better to control the other changes at the places they occur, and not in this component.

Drop v-model and replace it with manual handling.

It is important to note that "although a bit magical, v-model is essentially syntax sugar for updating data on user input events (...)". So without much harm you can remove it and handle the events directly, as you already almost do.

First, replace v-model with :value. The input will still follow the item.age updates, but will not update the age automatically.

<input type="text" name="qty"
       :value="item.age"
       @change="changeAge(item,$event)">

Then, in order to actually update the value, add item.age = event.target.value to changeAge, like this:

changeAge: function(item, event){
  alert(item.age);  // Old value
  alert(event.target.value);  // New value
  item.age = event.target.value;  // Actual assignment
}

Note: you may want to use @input instead, that's what v-model actually uses.

And that's it, it should do the trick. If you need to prevent the change, you can just omit the assignment, however, you need to reset the input value. item.age = item.age of Vue.set(item, 'age', item.age) may do the trick, but I'm not quite sure it will.

Note: event.target.value is a string, if you need a number, use parseInt().

Create a separate component instance for every item and create a watch there

In case you need to watch all the changes. However, if you need that, perhaps you should do that in some other place, not this component.

I don't have all the details ready, so I'll just the general draft: in your v-for, instead of plain <input>, you place another component, say <my-item>. You put v-model=item on it. Inside the component, you create the <input> and forward the value prop to it, as well as forward it's input/change events outside. In your component single item is a single prop, so you can attach a watcher directly to it. Relevant docs: Component Basics, Customizing Component v-model, Props.

2
  • If using an input[date], how do you reset the date displayed in the input from te called method ?
    – DevonDahon
    Commented Sep 20, 2019 at 6:06
  • You mean, an input with type="date"? I don't think they have a special support in Vue, and you'd have to treat the value as a string there? Perhaps the best option is to find some ready date picker component, that would support v-model - then it's about the same as a usual text input.
    – Frax
    Commented Sep 22, 2019 at 19:00
23

You dont get old value of element on-change as explained here. Also when watching an object, you can not get older value as explained here until the reference of object changes.

To work around these you can create a computed property which will be clone of your items data, and you can put a watcher over this computed property to get to know the old value as well, See working code below:

var app = new Vue({
  el:"#table1",
  data:{
   items:[{name:'long name One',age:21},{name:'long name Two',age:22}]
  },
  watch:{
    clonedItems: function(newVal, oldVal){
      alert(JSON.stringify(newVal));
      alert(JSON.stringify(oldVal));
    }
  },
  computed:{
    clonedItems: function(){
       return JSON.parse(JSON.stringify(this.items))
    }
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>

<table class="table table-cart" id="table1">
   <thead>
     <tr>
       <th>Name</th>
       <th>Age</th>
     </tr>
  </thead>
   <tbody>
     <tr v-for="(item,index) in items">
       <td>{{item.name}} :</td>       
       <td>
         <input type="text" name="qty"
                v-model="item.age">
       </td>
     </tr>
   </tbody>
 </table>

5
  • 2
    This means that for all changes this event is going to be fired. I need only for the item.age change event to occur. Your solution is good but is there a way to make it specific? Like if I make a backspace, the event is being fired also
    – madi
    Commented Feb 13, 2017 at 8:30
  • @madi one option can be to have a computed property, which returns age, and have watcher on it.
    – Saurabh
    Commented Feb 13, 2017 at 10:21
  • 2
    The thing is in computed property, we are not able to get the current item. So that is wat that confuses. Cannot believe Vue has such a big limitation
    – madi
    Commented Feb 13, 2017 at 14:43
  • return JSON.parse(JSON.stringify(this.items)); changes to this below: let newItems = this.items.slice(0);return newItems; Why returning a copy of the array, which does not work? And Why does yours work?
    – soarinblue
    Commented Jul 28, 2017 at 3:44
  • @soarinblue because you are not copying, you are just assigning the same reference to some other variable and returning that variable, so you are returning the same reference, which I am returning a new object after cloning it. You can as well use some other way mentioned here.
    – Saurabh
    Commented Jul 30, 2017 at 5:18
12

It may be too late. This worked for me as long as the function name inside the watch object is named as the variable which is watched. It only watches the specific variable.

watch:{
   items: function(newVal, oldVal){
      console.log("New value: "+ newVal + ", Old value: " + oldVal);
   },
   other_variable: function(newVal, oldVal){
      console.log("New value: "+ newVal + ", Old value: " + oldVal);
   }
}
5

replace v-model with value prop v-model is two-way binding, it will make your state complex then manually modify age property in change event

<input type="text" name="qty"
                :value="item.age"   
                @change="changeAge">



export default {
  change(val) {
     let ov = this.item.age
     let nv = val
     // do something
     // this.item.age = nv
   }
}
0
4

https://v2.vuejs.org/v2/api/#Vue-nextTick

 changeAge() {
             this.$nextTick(() => {
                // here the current values are set, do desired actions
                
            })
}
3

You can simply use

<input type="text" name="qty" v-model="item.age" @change="changeAge($event)">

and

changeAge: function(event){
  item = event.target.value;  // Actual assignment
}

in vue method object

3
  • 6
    where is the old value? Commented May 14, 2019 at 17:57
  • 1
    @Z.Khullah You should use :value instead of v-model to get the old value. Then use changeAge($event, qty) method to change values.
    – DevonDahon
    Commented Sep 23, 2019 at 3:22
  • 1
    @Z.Khullah The old value is then in qty and the new one in event.target.value.
    – DevonDahon
    Commented Sep 23, 2019 at 3:29
0

I had the same problem. I worked around it by wrapping the input in a Vue component and watching the property in the wrapper component. In the watcher, I called $emit, passing the new value, old value, and item as arguments.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>

<table class="table table-cart" id="table1">
    <thead>
        <tr>
            <th>Name</th>
            <th>Age</th>
        </tr>
    </thead>
    <tbody>
        <tr v-for="(item,index) in items">
            <td>{{item.name}} :</td>       
            <td>
                <item-age-input
                    :item="item"
                    @age-changed="ageChanged"></item-age-input>
            </td>
        </tr>
    </tbody>
</table>

<script>
Vue.component('item-age-input', {
    props: {
        item: {
            type: Object,
            required: true
        }
    },
    template: `<input type="text" name="qty" v-model="item.age">`,
    watch: {
        "item.age": function(newAge, oldAge) {
            this.$emit('age-changed', newAge, oldAge, this.item);
        }
    }
});

var app = new Vue({
    el:"#table1",
    data:{
        items:[{name:'long name One',age:21},{name:'long name Two',age:22}]
    },
    methods: {
        ageChanged: function(newAge, oldAge, item){
            alert(newAge + ', ' + oldAge);
            console.log(item);
        }
    }
});
</script>
0

Vue 3.3 update, your onChange handler can accept a first argument that will be the new value.


<my-component
  v-model="firstName"
  @update:model-value="onFirstNameChanged"
/>

const firstName = ref<string | null>(null)

const onFirstNameChange = (newValue: string | null) => {
  console.log('will be your old value', firstName.value);
  console.log('will be your new value', newValue);
};

Not the answer you're looking for? Browse other questions tagged or ask your own question.