When should a 'watcher' be used over an 'event'? - vue.js

I'm under the impression that watchers should be used as a last resort in Vue. I know that computed properties are favoured over watchers. However, there are times when you can use a watcher or an #change to do the same thing. I feel like an event is a better and more explicit way of performing an operation in the following case:
From VueJS Guide - Computed Properties and Watchers
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// whenever question changes, this function will run
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
_.debounce(this.getAnswer, 500)
}
},
...
}
or you could do this with an #change or #input event like this:
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question" #change="getAnswer">
</p>
<p>{{ answer }}</p>
</div>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
methods: {
// whenever question changes, this function will run
getAnswer: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
_.debounce(this.getAnswer, 500)
}
},
...
}
I'm not sure why a watcher would be used here over an #change or similar event. I feel that it's better to use an event in this case because it is more explicit.
So my questions are:
Am I performing the operation inefficiently in the second example?
What are the drawbacks to using an event if there are any?
What are the drawbacks to using a watcher here if there are any?
The guide I'm referencing in the first example states watchers are "useful when you want to perform asynchronous or expensive operations in response to changing data", however can one not just use an event and an async method to perform the asynchronous operations?

Related

Vue JS - Trigger Method if input lost focus

Quick question about Vue JS.
On my website, I have got a shopping cart and the user can enter any quantity. I am saving this quantity to the database table when he enters it...
issue is, the input field keeps firing save method every single digit of user types. For example, if the user types 123, the save method called 3 times. 1 and 12 and 123
I just want to call this save method when this input loses the focus, so I can save only once, not every single digit.
Component
Vue.component('product-counter', {
props: ['quantity'],
data: function () {
return {
count: this.quantity
}
},
template: `
<div class="input-group ">
<input v-bind:value="quantity" v-on:input.focus="$emit('input', $event.target.value)" type="text" class="form-control col-2">
</div>
`
})
Component Call
<product-counter
v-bind:productcode="item.productCode"
v-bind:quantity="item.quan"
v-on:input="item.quan=updateItemQuantity($event,item.productCode)"
v-on:increment-quantity="item.quan=updateItemQuantity(item.quan,item.productCode)"
v-on:decrement-quantity="item.quan=updateItemQuantity(item.quan,item.productCode)"></product-counter>
Vue: Method
"updateItemQuantity": function (totalquantity, pcode) {
if (totalquantity != '') {
... Update Database...
}
}
You're listening to the input event, which is triggered every time the value of the input changes, so every time a character is typed or removed.
Instead, you should listen to the blur event, which only fires when an input loses focus.
You can pass this along through your component, the same way you pass through the input event.
TLDR: Couple UpdateItemQuantity to v-on:blur instead of v-on:input, and make sure to $emit the blur event from your products-counter component.
Tip: Separate the client-side state (item.quan) and your server-side 'state' (your database) into two different methods. You want the value to reflect what the user is typing in real-time (input), which conflicts with what you want for updating the database (not real-time, only on blur). Otherwise you may get cases where users can't see what they type, as item.quan is never updated.
I think you just need to use #change instead of #input
It could be that you should use blur event.
Vue:
new Vue({
el: "#app",
data: {
quantity: ''
},
methods: {
printConsole: function () {
console.log('blured');
}
}
})
html:
<div id='app'>
<input v-bind:value="quantity" v-on:blur="printConsole" type="text" class="form-control col-2">
</div>
see jsfiddle for reference: https://jsfiddle.net/erezka/h8g62xfr/11/
blur will emit your change only after you focus out of the input

Vue: How to perform reactive object change detection in v-for?

I read this documentation but cannot use the proposed solution.
I have a v-for loop over objects. These objects are changed dynamically over time and I need that change to show reactively in the v-for loop.
<b-row lg="12" v-for="data in objects" :key="data.id">
<div v-if="data.loading">
loading...
{{data.loading}}
</div>
<div v-else>
loaded, done
{{data.loading}}
</div>
</b-row>
In my methods, I have a for loop that downloads data for each object and changes the object value like this:
for(var i = 0; i<response.ids.length; i++){
var newId = response.ids[i].id
this.objects.newId = {"loading":true, "id": newId}
downloadSomething(newId).then(res => {
this.objects.newId = res[0] //<-- this change needs to be shown reactively.
})
}
According to Vue documentation, Object changes are not reactive:
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive
Vue propses some workaround like this:
Vue.set(vm.userProfile, 'age', 27)
UPDATE
But for my case, this just creates a new parameter in the object with the same ID and creates a duplicate key warning and other problems.
I also tried Vue.delete just before Vue.set but it is not actually deleting.
Is there a way to not replace the key/value pair but add more/change parameters to the first child of the root with the ID of newID
Thanks!
Solution: Replace this.objects.newId = res[0] with this.$set(this.objects, 'newId', res[0]) and it should work.
Explanation: this.$set is just an alias to Vue.set, available within any Vue instance. Bear in mind that vm (stands for view model) in the example is the same as this and equals to Vue component instance. Also due to ES5 JS restrictions, you have to set objects' properties explicitly via Vue.set/this.$set to manually trigger re-render. This problem will be resolved when Vue 3.0 is released.
Hope this helps, if you need any clarifications - feel free to ask.
Try that:
downloadSomething(newId).then(res => {
this.$set(this.objects, 'newId', res[0])
})
you need Vue.set() when you want to define a new property to an existing object (not directly to the data), and this function will assign the new property to the built-in watcher, and it will become reactive as well.
its all being explained in the docs: https://v2.vuejs.org/v2/guide/reactivity.html
in your case, it seems to be all you need to make it work. in my example:https://jsfiddle.net/efrat19/eywraw8t/484131/ an async function fetches the data and define a new property to contain te response.
new Vue({
el: "#app",
data: {
objects:{
property1:12
}
},
methods: {
fetchDataAndAddProperty(response){
fetch('https://free.currencyconverterapi.com/api/v6/countries').then(res =>
res.json()).then(data => Vue.set(this.objects,'property2',data))
}
}
})
and the template:
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<div lg="12" v-for="(val,key,index) in objects" :key="index">
{{key}}:{{val}}
</div>
<button #click="fetchDataAndAddProperty()">fetch</button>
</div>
as you can see in the fiddle, the new property becomes reactive and being displayed as well.

Why computed property in vue.js?

I am a beginner to vue.js. What is the reason to use computed property instead of methods. Why am asking this question is, because the both computed property and methods do the same thing
Methods can get attributes and need to be called manually, computed is not.
Also you no need to clone code while multiple using. Think about computed properties like a shortcut to you additional logic.
Putting too much logic in your templates can make them bloated and hard to maintain. For example:
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
The same, using computed property:
<div id="example">
{{ reversedMessage }}
</div>
Vue code:
var vm = new Vue({
el: '#example',
data: {
message: 'Hello'
},
computed: {
// a computed getter
reversedMessage: function () {
// `this` points to the vm instance
return this.message.split('').reverse().join('')
}
}
})
Difference
But main difference as i think, is caching. When you call method 5 times, you get 5 computings. On the other side a computed property is computing only once (when changing), and then return a cached value.

How do I update semantic ui dropdown list with vue?

I'm trying to update the semantic ui dropdown with new values. Vue is correctly being updated and I'm refreshing the semantic ui dropdown but it still isn't updating. I saw another post which mentioned the use of key, but it still fails.
Template
<div id=root>
<label>Type:</label>
<select id="app_type" class="ui search selection dropdown" v-model="model_type_val">
<option v-for="model_type in model_types" v-bind:value="model_type.value" v-bind:key="model_type.value">{{model_type.text}}</option>
</select>
<p>
selected: {{model_type_val}}
</p>
</div>
Code
var model_types2= [
{value:"",text:"Type"},
{value:"type1",text:"Type1a"},
{value:"type2",text:"Type2a"},
{value:"type3",text:"Type3a"},
{value:"type4",text:"Type4"}
];
var vm2= new Vue({
el:'#root',
data:{
model_type_val:"",
model_types:[
{value:"",text:"Type"},
{value:"type1",text:"Type1"},
{value:"type2",text:"Type2"},
{value:"type3",text:"Type3"}
]
},
mounted: function(){
$('#app_type').dropdown();
setTimeout(function() {
this.model_types=model_types2;
alert(this.model_types[1].text);
$('#app_type').dropdown('refresh');
}, 1000);
}
});
I've tried to reproduce the code in this jsfiddle.
You have a this problem. When you have a callback inside a Vue method or lifecycle hook in which you use this, you need to make sure that this points to the correct object (the Vue). You do that with an arrow function, a closure, or bind.
setTimeout(() => {
this.model_types=model_types2;
$('#app_type').dropdown('refresh');
}, 1000);
Here is your fiddle updated.
Note: In the fiddle, I also converted your selector to use a ref. Typically you want to start weaning yourself off jQuery when working with Vue.
See How to access the correct this inside a callback.

In vue.js is it possible to notify "observers" to refetch the value from the observed data, without changing the value of the observed data

Suppose that I have an input element bound like this:
<input :value="somedata">
The user types something in the input, and since I am not using v-model or altering somedata through a handler, the value of the element is now different from somedata. This is what I want, but I would also like to have the following capability:
Without changing the value of somedata I would like to be able to notify the element so that it sets its value equal to somedata again. Something like knockout's notifySubscribers() or valueHasMutated()
Is that possible in vue.js?
UPDATE: A clear illustration of the issue here: https://jsfiddle.net/gtezer5c/3/
It's a little difficult interpreting what exactly the requirements and acceptance criteria might be to suit your needs, and I thought Bill's solution was what you were after, but after all the updates and clarifications, I think I understand a little more what you're trying to accomplish: in short, I think you need a generic way to have an input that can hold a value but that can be independently reverted to some other value.
Please have a look at this CodePen. I believe it's providing what you're trying to do. It allows you to create an input element as a revertable component, which can optionally be passed a default value. Any changes to that input are maintained by the component with its value data property. It will not be observing/pulling in any lastKnownGood type of value because any reversion will be pushed into the component from outside.
Externally to the revertable component, you can $emit a revert event with a new value that will cause either all revertable components or a single revertable component (with a matching ID) to be reverted.
I feel like it's mostly a clean solution (assuming I'm understanding the requirements correctly), except that in VueJS 2 we have to use a standalone, shared Vue object to pass the events when there is no parent-child relationship. So you'll see:
const revertBus = new Vue()
defined in global scope in the demo. And the revertable component will use it to receive incoming messages like so:
revertBus.$on('revert', (value, id) => { ... }
and the controlling Vue object that is triggering the messages will use it like this:
revertBus.$emit('revert', this.reversionValue, targetId)
You can also emit the event with a null value to cause the revertable component to reset its value to its initial default value:
revertBus.$emit('revert', null, targetId)
Again, it's a mostly clean solution, and though it might not fit perfectly inline with what you're trying to accomplish, I'm hoping it might at least help you get closer.
I'm not sure I'm following properly but I'll give it a shot.
What I think you want is to only update some values when their "temporary" values meet some type of condition. Here's how I was thinking of it.
<div id="app">
<input v-model="tempValues.one">
<input v-model="tempValues.two">
<input v-model="tempValues.three">
<pre>{{ values }}</pre>
<pre>{{ tempValues }}</pre>
</div>
Then, in my component, I watch tempValues and only update values when a condition is met.
new Vue({
el: '#app',
data: {
values: {
one: '',
two: '',
three: '',
},
tempValues: {},
},
created () {
// Create the tempValues based on the real values...
this.tempValues = Object.assign({}, this.values)
},
methods: {
updateValues (tempValues) {
// Only updating the values if all the tempValues are longer than 3 characters...
var noneEmpty = Object.values(tempValues).every((value) => value.length > 3)
if (noneEmpty) {
this.values = Object.assign({}, tempValues)
}
},
},
watch: {
// Watch tempValues deeply...
tempValues: {
handler (tempValues) {
this.updateValues(tempValues)
},
deep: true,
},
},
})
Here's a quick demo: https://jsfiddle.net/crswll/ja50tenf/
yourvar.__ob__.dep.notify()
works on objects and arrays
Yes, You should be able to do this with help of v-on:input. You can call a function on input and put your logic of checking and updating in this function:
<input :value="somedata" v-on:input="yourMethod">
In fact if you look at the documentation, <input v-model="something"> is syntactic sugar on:
<input v-bind:value="something" v-on:input="something = $event.target.value">
so instead of assigning variable something to value inputted, you can put your logic in that place.