Why this computed property is showing changes (Vue) - javascript

I am changing properties using some buttons. When I click the buttons the data is updated in the UI, even the computed1, but not the console.log("computed1"). I think it's because I am changing its properties and not the entire object. But if it's not triggered, why the UI is updated ? Could you explain me ? I couldn't find something like this in the documentation.
Code: https://jsfiddle.net/1hr7cy5d/
var example1 = new Vue({
el: '#example',
data: function() {
return {
item: {
aaa: 'aaa',
bbb: 'bbb',
ccc: 'ccc'
}
}
},
computed: {
computed1: function() {
console.log("computed1");
let item = this.item
return item;
},
},
methods: {
method1() {
console.log("method1")
this.item.aaa = 'xxxx';
},
method2() {
console.log("method2")
this.item["bbb"] = 'yyyyy';
},
method3() {
console.log("method3")
this.$set(this.item, "ccc", "zzzzz")
},
method4() {},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="example">
<button #click="method1()">method1</button>
<button #click="method2()">method2</button>
<button #click="method3()">method3</button>
<button #click="method4()">method4</button>
<pre>{{item}}</pre>
<pre>{{computed1}}</pre>
</div>

Related

Vue add new value to array

I am new to VUE and for one project I am trying to update an array passing the object. I have two button which is called BUTTON 1 and BUTTON 2. If you click on BUTTON 1 then it will set an object to list[] using this.$set. But when BUTTON 2 is pressed it should update with the new value and should display as
{
a:1,
b:2,
c:3
}
Right now I am using this.$set for button 2 as well and it removes the previous value then adds the new one as {c:3} only. Is there a way to add on value using VUE to display { a:1,b:2,c:3} when BUTTON 2 is clicked.
View
<div id="app">
<button #click="button1()">Button 1</button>
<button #click="button2()">Button 2</button>
</div>
Method
new Vue({
el: "#app",
data: {
list:[]
},
methods: {
button1(){
var b = '0';
this.$set(this.list, b, {
1:{
a: '1',
b: '2'
}
})
console.log(this.list);
},
button2(){
var b = '0';
this.$set(this.list, b,{
1:{
c: '3'
}
})
console.log(this.list);
},
}
})
Below there is a jsfiddle link for my code
https://jsfiddle.net/ujjumaki/enj3dwo6/19/
Hope this works.
new Vue({
el: "#app",
data: {
list:[]
},
methods: {
button1(){
console.log('button 1');
const b = '0';
const restObj=this.list[b] && this.list[b][1]
this.$set(this.list, b, {
1:{
...restObj,
a: '1',
b: '2'
}
})
console.log(this.list);
},
button2(){
const b = '0';
const restObj=this.list[b] && this.list[b][1]
this.$set(this.list, b,{
1:{
...restObj,
c: '3'
}
})
console.log(this.list);
},
}
})

Vue: "_" prefixed property not working in data property

I am trying to set a computed property as prop to a child component in Vue.js.
This is an excerpt of the component code (Here's the full (not) working example):
<div id="app">
<show-me :title="aTitle"></show-me>
</div>
const ShowMe = {
data() {
return {
_title: null
}
},
computed: {
title: {
set: function(val) {
this._title = val;
},
get: function() {
return this._title;
}
}
},
template: `
<div>
should display title: {{title}} <br/>
but instead typeof title: {{typeof title}}
</div>`
};
const vm = new Vue({
el: '#app',
data: {
aTitle: 'A Title'
},
components: {
'show-me': ShowMe
}
});
When running the example the component's title value in the template is undefined. It's pretty simple and straightforward and I don't get it why it's not working.
_ prefixed properties are reserved for Vue's internal properties. They are not available for direct binding (but you can bind to them as
$data._message) - Evan You (Vue creator)
You can't use _ prefixed values cuz it get treated as internal properties in vue system
Reference - https://github.com/vuejs/vue/issues/2098
Most proper explanation available at Docs too -
Properties that start with _ or $ will not be proxied on the Vue
instance because they may conflict with Vue’s internal properties and
API methods. You will have to access them as vm.$data._property
https://v2.vuejs.org/v2/api/#data
So In your case you will do replace this._title with this.$data._title
const ShowMe = {
data() {
return {
_title: null
}
},
computed: {
title: {
set: function(val) {
this.$data._title = val;
},
get: function() {
return this.$data._title;
}
}
},
template: `
<div>
should display title: {{title}} <br/>
but instead typeof title: {{typeof title}}
</div>`
};
const vm = new Vue({
el: '#app',
data: {
aTitle: 'A Title'
},
components: {
'show-me': ShowMe
}
});
const ShowMe = {
props:{
title:{
type:String,
required:true
}
},
template: `
<div>
should display title: {{title}} <br/>
but instead typeof title: {{typeof title}}
</div>`
};
or you can use simply as follows
const ShowMe = {
props:['title'],
template: `
<div>
should display title: {{title}} <br/>
but instead typeof title: {{typeof title}}
</div>`
};
This code works. It will be more consistent practice to avoid using this.$data, though the other answer is correct in that you can certainly use it. It is better to avoid the underscores altogether and find a better naming convention. The naming in this example also isn't best practice.
const ShowMe = {
data() {
return {
cTitle: null
}
},
computed: {
title: {
set: function(val) {
this.cTitle = val;
},
get: function() {
return this.cTitle;
}
}
},
template: `
<div>
should display title: {{title}} <br/>
but instead typeof title: {{typeof title}}
</div>`
};
const vm = new Vue({
el: '#app',
data: {
aTitle: 'A Title'
},
components: {
'show-me': ShowMe
}
});
This code will allow you to initialize the initial value and then reactively change the dataTitle field inside the ShowMe component.
If you want to change a property from an external component, then you should do without the dataTitle field and use only props.
const ShowMe = {
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
dataTitle: this.title
}
},
template: `
<div>
should display title: {{dataTitle}} <br/>
but instead typeof title: {{typeof dataTitle}}
</div>`
};
You may also need the ability to change the property both inside ShowMe and from an external component. Then you can use the following code:
JS
const ShowMe = {
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
dataTitle: this.title
}
},
watch: {
title (newValue, oldValue) {
if (newValue !== oldValue) {
this.dataTitle = newValue;
}
},
dataTitle (newValue, oldValue) {
if (newValue !== oldValue) {
this.$emit('changeTitle', newValue);
}
}
},
template: `
<div>
should display title: {{dataTitle}} <br/>
but instead typeof title: {{typeof dataTitle}}
</div>`
};
const vm = new Vue({
el: '#app',
data: {
aTitle: 'A Title'
},
components: {
'show-me': ShowMe
}
});
HTML
<div id="app">
<show-me :title="aTitle" #changeTitle="aTitle = $event"></show-me>
</div>

Vue Watch doesnt Get triggered when using axios

Hey guys I have this code that fetches data from database usin axios, and in the .then() function I set a data property, watch doesnt trigger. Here is some code that I currently have. And thank you in advance!
export default {
name: '..',
data() {
return {
autocompleteOn: false
}
},
watch: {
autocompleteOn(oldVal, newVal) {
console.log('autocomplet') // doesnt trigger this
}
},
methods: {
fetchAutocompleteResults: _.debounce((filter) => {
let $this = this;
let data = {
filter: filter,
page: $this.page
};
filter.resources.response = [];
filter.loading = true;
axios.post(BASE_URL + '/search/filter', data).then(function(response) {
if (response.data.length) {
filter.autocompleteOn = true;
$this.autocompleteOn = true;
filter.resources.response = filter.resources.response.concat(response.data);
$this.currentFilter = filter;
$this.page++;
console.log($this.autocompleteOn); // this is correct
}
filter.loading = false;
});
}, 300)
}
}
The debounce with an arrow function is making the this be something other than the Vue instance (e.g. window).
Instead of:
methods: {
fetchAutocompleteResults: _.debounce((filter) => {
Use:
methods: {
fetchAutocompleteResults: _.debounce(function (filter) {
// ^^^^^^^^ ^^^
Demo:
new Vue({
el: '#app',
data() {
return {
autocompleteOn: false
}
},
watch: {
autocompleteOn(oldVal, newVal) {
console.log('autocomplet') // doesnt trigger this
}
},
methods: {
fetchAutocompleteResults: _.debounce(function (filter) { // CHANGED from arrow function
let $this = this;
let data = {
filter: filter,
page: $this.page
};
filter.resources.response = [];
filter.loading = true;
// changed data for demo
data = [{title: 'foo', body: 'bar', userId: 1}];
// changed URL for demo
axios.post('https://jsonplaceholder.typicode.com/posts', data).then(function(response) {
if (response.data.length) {
filter.autocompleteOn = true;
$this.autocompleteOn = true;
filter.resources.response = filter.resources.response.concat(response.data);
$this.currentFilter = filter;
$this.page++;
console.log($this.autocompleteOn); // this is correct
}
filter.loading = false;
});
}, 300)
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>
<div id="app">
<button #click="fetchAutocompleteResults({resources: {}})">fetchAutocompleteResults</button>
</div>

vue.js - recursive components doesn't get updated when using nested array from raw json

I am trying to create a tree with an add function using computed data. I used the tree-view example in vuejs official homepage and combined it with the computed function that I created but found no luck in implementing it. I've been trying to solve this for 4 days already and still no luck so I am here looking for help.
When you click the "+" in the end of the list it will trigger a call to the addChild function and it will successfully append the data. The data gets appended but the recursive component is not reactive.
https://jsfiddle.net/znedj1ao/9/
var data2 = [{
"id": 1,
"name":"games",
"parentid": null
},
{
"id": 2,
"name": "movies",
"parentid": null
},
{
"name": "iron-man",
"id": 3,
"parentid": 2
},
{
"id": 4,
"name": "iron-woman",
"parentid": 3
}
]
// define the item component
Vue.component('item', {
template: '#item-template',
props: {
model: Object
},
data: function () {
return {
open: false
}
},
computed: {
isFolder: function () {
return this.model.children &&
this.model.children.length
}
},
methods: {
toggle: function () {
if (this.isFolder) {
this.open = !this.open
}
},
changeType: function () {
if (!this.isFolder) {
Vue.set(this.model, 'children', [])
this.addChild()
this.open = true
}
},
addChild: function () {
this.model.children.push({
name: 'My Tres',
children: [
{ name: 'hello' },
{ name: 'wat' }
]
})
}
}
})
// boot up the demo
var demo = new Vue({
el: '#demo',
data: {
treeData2: data2
},
computed: {
nestedInformation: function () {
var a= this.nestInformation(data2);
return a;
}
},
methods:
{
nestInformation: function(arr, parent){
var out = []
for(var i in arr) {
if(arr[i].parentid == parent) {
var children = this.nestInformation(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}
}
})
<!-- item template -->
<script type="text/x-template" id="item-template">
<li>
<div
:class="{bold: isFolder}"
#click="toggle"
#dblclick="changeType">
{{model.name}}
<span v-if="isFolder">[{{open ? '-' : '+'}}]</span>
</div>
<ul v-show="open" v-if="isFolder">
<item
class="item"
v-for="model in model.children"
:model="model">
</item>
<li class="add" #click="addChild">+</li>
</ul>
</li>
</script>
<p>(You can double click on an item to turn it into a folder.)</p>
<!-- the demo root element -->
<ul id="demo">
<item
class="item"
:model="nestedInformation[1]">
</item>
</ul>
The Vue.js documentation that Abdullah Khan linked to in a comment above says:
Again due to limitations of modern JavaScript, Vue cannot detect property addition or deletion.
However, property addition is exactly what you are doing in your nestInformation method:
if(children.length) {
arr[i].children = children
}
The result is that the children property of each object is not reactive, so when this Array is pushed to in addChild, no re-render is triggered in the UI.
The solution will be to use Vue.set when creating the children Arrays so that they will be reactive properties. The relevant code in the nestInformation method must be updated to the following:
if (children.length) {
Vue.set(arr[i], 'children', children);
}
I have forked and modified your fiddle for reference.

Vue - passing filter criteria when filtering array in methods

I was trying to filter listItems by filterCriteria before rendering.
The problem is that when I use Array.prototype.filter, I can't pass the filterCriteria into the filter function.
Is there any good way to pass it in without creating a new function?
Thanks!
var todoList = new Vue({
el: '#todolist',
data: {
// I want to use filterCriteria in data as filter criteria
filterCriteria: 'Done',
listItems: [
{
content: 'Fishing.',
status: 'Done',
},
{
content: 'Do homework.',
status: 'Ongoing',
}
],
},
methods: {
filterList(listItems) {
return listItems.filter(function(item) {
// Ideally the 'Ongoing' here should be "filterCriteria" in data property
return item.status === 'Ongoing';
});
}
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="todolist">
<div class="list-filter">
Ongoing
Done
</div>
<div v-for="item in filterList(listItems)" class="list-item">
<span class="list-status">{{item.status}}</span>
<span class="list-content">{{item.content}}</span>
</div>
</div>
...can't pass the filterCriteria into the filter function.
Is there any good way to pass it in without creating a new function?
Yes, it is. First, simplier, use arrow function for callback:
filtered () {
return this.items.filter(item => item.status === this.query)
}
Second, legacy, save this object reference:
filtered () {
var vm = this
return this.items.filter(function (item) {
item.status === vm.query
})
}
Complete example:
var todoList = new Vue({
el: '#todolist',
data: {
query: 'Done',
items: [
{content: 'Fishing.', status: 'Done'},
{content: 'Do homework.', status: 'Ongoing'}
]
},
computed: {
filtered () {
return this.items.filter(item => item.status === this.query)
}
}
})
<div id="todolist">
<div>
<button #click="query='Ongoing'">Ongoing</button>
<button #click="query='Done'">Done</button>
</div>
<div v-for="item, idx in filtered" :key="idx">
<span>{{item.status}}:</span>
<span>{{item.content}}</span>
</div>
</div>
<script src="https://unpkg.com/vue"></script>

Categories

Resources