How to display the value which is inside computed in VueJS2? - vuejs2

How to display the value which is inside computed in VueJS2 ? Actually i'm trying to return the value click*2 using computed property. So i'm displaying the output using the expression {{counter}}. But i'm not getting any output, at first it was set to counter to 0 (stored in the data). Even i didn't use v-once. So why my counter value is not updating through computed property?
<div id="app4">
<p>counter : {{ counter }}</p>
<p>clicks : {{ click }}</p>
<button v-on:click="increment">increment</button>
</div>
<script>
var vm = new Vue({
el: '#app4',
data: {
counter:0,
click: 0,
},
methods: {
increment() {
this.click++;
}
},
computed: {
counter() {
return this.click * 2;
}
}
});
</script>

Remove counter from data and it will work. You have two properties that shadow one another - one in data and the other in computed.

Related

Vue JS Sort Date with components using Computed Property

I tried to sort the date using computed properties but it didnt work, if i using at methods and remove the slice, then it will sort as expected. I've tried to split the date also but still not working. I am not sure what caused the problem.
Kindly find my code as below.
App
<template>
<div>
<h2>Ingredients</h2>
<ul>
<li></li>
</ul>
<ingredients-list v-for="(ingredient,index) in ingredients"
:key="index"
:index='index'
:foodName="ingredient.food"
:foodExpiry="ingredient.expiryDate">
</ingredients-list>
</div>
</template>
<script>
export default {
data(){
return{
ingredients:[
{
food:'carrot',
expiryDate:'2020-12-12'
},
{
food:'papaya',
expiryDate:'2018-1-15'
},
{
food:'orange',
expiryDate:'2021-10-13'
},
{
food:'meat',
expiryDate:'2019-4-23'
}]
}
},
computed: {
sortedItems() {
return this.ingredients.slice().sort( ( a, b) => {
return new Date(a.expiryDate)- new Date(b.expiryDate);
});
}
}
}
</script>
components
<template>
<div>
<h2>{{index}}</h2>
<h2>Food:{{foodName}}</h2>
<h2>Expiry:{{foodExpiry}}</h2>
</div>
</template>
<script>
export default {
props:['foodName','foodExpiry'],
}
</script>
Like #Anatoly said
Computed props are never calculated if they are not used at all.
What you should be doing to solve the problem is :
Using slice method instead of splice since it mutates the original array:
sortedItems() {
return this.ingredients.slice()
.sort((a, b) => new Date(a.expiryDate)- new Date(b.expiryDate));
}
Loop through the computed property and not the original array:
<ingredients-list v-for="(ingredient,index) in sortedItems"
:key="index"
:index='index'
:foodName="ingredient.food"
:foodExpiry="ingredient.expiryDate">
</ingredients-list>
It seems you confused splice with slice. You need slice to get a copy of an array and to sort this copy:
sortedItems() {
return this.ingredients.slice().sort( ( a, b) => {
return new Date(a.expiryDate)- new Date(b.expiryDate);
});
}

Vue stored valued through props not being reactive

So I pass value using [props] and stored it in child component's data. However, when passing [props] value changes from parent, it's not updating in child component's data. Is there a fix for this..?
Here is the link to w3 test (I tried to clarify the problem as much as possible here)
<div id='app'>
<div id='parent'>
<button #click='current_value()'>Click to see parent value</button>
<br><br>
<button #click='change_value($event)'>{{ txt }}</button>
<br><br>
<child-comp :test-prop='passing_data'></child-comp>
</div>
<br><br>
<center><code>As you can see, this methods is <b>NOT</b> reactive!</code></center>
</div>
<script>
new Vue({
el: "#parent",
data: {
passing_data: 'Value',
txt: 'Click to change value'
},
methods: {
current_value(){
alert(this.passing_data);
},
change_value(e){
this.passing_data = 'New Vaule!!';
this.txt = 'Now click above button again to see new value';
e.target.style.backgroundColor = 'red';
e.target.style.color = 'white';
}
},
components: {
"child-comp": {
template: `
<button #click='test()'>Click here to see child (stored) value</button>
`,
props: ['test-prop'],
data(){
return {
stored_data: this.testProp
}
},
methods: {
test(){
alert(this.stored_data);
}
},
watch: {
stored_data(){
this.stored_data = this.testProp;
}
}
}
}
});
Props have one way data flow, that's why it doesn't react when you update it from the parent component. Define a clone of your prop at data to make it reactive, and then you can change the value within the child component.
Short answer: you don't need stored_data. Use alert(this.testProp) directly.
Long answer: when child component is created, stored_data get it's value from this.testProp. But data is local, it won't change automatically. That's why you need to watch testProp and set it again. But is not working because of a simple mistake, your watch should be:
watch: {
testProp(){ // here was the mistake
this.stored_data = this.testProp;
}
}

Computed property doesn't update upon changing data variable

I have a data variable named monRaw that is a Moment object representing the first day of current week:
...
data() {
return {
monRaw: moment().startOf('isoWeek')
}
},
...
Then I have a computed property named weekTitle that displays it along with some text (it does more than that in my project, but I simplified it here):
...
computed: {
weekTitle: function() {
alert('updating computed property...');
return `Current week starting on ${this.monRaw.format('DD')}`;
}
},
...
Finally, a method named viewNextWeek() changes my data variable to the following week's first day:
...
methods: {
viewNextWeek: function() {
this.monRaw = this.monRaw.add(1, 'weeks');
},
...
In my template, I display my computed property along with a button triggering my method:
{{ weekTitle }}
<button #click="viewNextWeek">Next Week</button>
For some reason though, my computed property does NOT get updated when I change the data variable it's based on.
It doesn't even try to: my alert() gets triggered on page load only, not any more after that when clicking the button.
What's wrong here?
Moment.add mutates the moment variable, so you are just assigning the same object back to itself, which results in no change.
Per the docs:
If you want to create a copy and manipulate it, you should use
moment#clone before manipulating the moment.
new Vue({
el: '#app',
data: {
monRaw: moment().startOf('isoWeek')
},
computed: {
weekTitle: function() {
alert('updating computed property...');
return `Current week starting on ${this.monRaw.format('DD')}`;
}
},
methods: {
viewNextWeek: function() {
const newValue = this.monRaw.clone().add(1, 'weeks');
this.monRaw = newValue;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
{{ weekTitle }}
<button #click="viewNextWeek">Next Week</button>
</div>

Computed object not updated using sync modifier

I have a computed property that simply formats the date:
computed: {
items() {
return this.licenseItems.map(function(license) {
license.expires_at = moment(license.expires_at).format('MM/DD/YYYY');
return license;
});
}
}
I pass licenseItems to a form component with the .sync modifier and emit update:field event from the form. In vue dev tools, I can see that licenseItems (data) is properly updated, but items (computed) is still showing the old data so no re-computation was performed.
I have noticed that if I remove the map and just return the licenseItems object from the computed property, it is updated. Is there some issue with Vue's computed property on mapped objects when using the sync modifier?
You should be aware that you're modifying underlying objects in your computed. From your fiddle:
computed: {
mapped: function() {
return this.items.map(function(item) {
let original = item.date;
item.date = moment(item.date).format('MM/DD/YYYY');
console.log(original + ' => ' + item.date);
return item;
});
}
}
Your incrementDates function also modifies the underlying objects directly.
Because each element of items is an object, item is a reference to that same object, so your routine updates the members of items itself. If you intend to modify the object values, you should use a watch instead of a computed. If you want to have a proper computed that does not modify data items, you need to deep copy the objects.
In my example below, you can see that the data value and the computed value are distinct, but the computed is based on the data. Also, the update event works with sync to update the value in the parent, rather than the value being updated directly in the component. You can enter any format of date that Date understands to set the value.
new Vue({
el: '#app',
data: {
licenseItems: [{
expires_at: Date.now()
}]
},
computed: {
items() {
return this.licenseItems.map(function(license) {
const newItem = Vue.util.extend({}, license);
newItem.expires_at = moment(license.expires_at).format('MM/DD/YYYY');
return newItem;
});
}
},
components: {
myUpdater: {
props: ['items'],
methods: {
doUpdate(event, index) {
const newObj = this.items.map(item => Vue.util.extend({}, item));
newObj[index].expires_at = new Date(event.target.value);
console.log("new object", newObj);
this.$emit('update:items', newObj);
}
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.1/moment.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<my-updater :items.sync="licenseItems" inline-template>
<div>
<input v-for="item, index in items" :value="item.expires_at" #change="doUpdate($event, index)">
</div>
</my-updater>
<div v-for="item in items">
{{item.expires_at}}
</div>
</div>

How to obtain children components inside Vue.js app

So, I have an app with multiple child components. Basically a spreadsheet.
I want to be able to calculate the sum of the components when any cell changes. I've figured out a way to store all the values of the cells by caching them when a change event is propagated. But, is this the best way? Is there a better way to dynamically grab the children? I understand props are the way to send data down, but how do I pull data up?
This is the HTML:
<html>
<head>
</head>
<body>
<span id="calculator">
<template v-for="i in 5">
<cell v-bind:index="i" v-on:total="total"></cell>
</template>
{{ subtotal }}
{{ cells }}
</span>
<script src="vue.js"></script>
<script src="app.js"></script>
</body>
</html>
And the app.js:
Vue.component( 'cell', {
template: "<input v-model='value' v-on:change='total' size='10' type='text'/>",
props: {
index: Number
},
data: function() {
return {
value: 0
};
},
methods: {
total: function() {
console.log( "Value is now: " + this.value + " for index: " + this.index )
this.$emit( 'total', this.value, this.index )
}
}
});
var app = new Vue( {
data: {
subtotal: 0,
cells: []
},
el: "#calculator",
methods: {
total: function( value, indexPlusOne ) {
var index = indexPlusOne-1;
var v = parseInt( value );
Vue.set( this.cells, index, v);
console.log( "Inside the total function: " + v + " " + index );
this.subtotal = 0;
for( var i = 0; i < this.cells.length; i++ ) {
if( this.cells[i] ) {
this.subtotal += this.cells[i];
}
}
}
}
});
I understand props are the way to send data down, but how do I pull data up?
The best way is to use v-model for your custom cell component to pull data up.
Ref: https://v2.vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events
As explained in the link above, <input v-model="something"> is a syntactic sugar for:
<input v-bind:value="something" v-on:input="something = $event.target.value">
So, your ideal solution would be like:
<cell v-model="item" v-for="item in all_cell_items"></cell>
From within the cell component, you may pass value back to parent (root) component by: this.$emit("input", newValue). The parent component (root) remains clean, and you may simply use a computed property for subTotal.
But this will not work if you have a plain list of integers, like this.cells = [1,2,3,4] and attempt to pass the values to cell component using v-model. You will get the following error:
[Vue warn]: : You are binding v-model directly to a v-for iteration alias. This will not be able to modify the v-for source array because writing to the alias is like modifying a function local variable. Consider using an array of objects and use v-model on an object property instead.
If you are ok to modify your this.cells to array of objects, then you have a clean way of doing it like:
<cell v-model="item.price" :label="item.name" v-for="item in all_items"></cell>
Here is a working jsFiddle for this example: https://jsfiddle.net/mani04/9b7n3qmt/