Given that a colon indicates one-way-data-binding in VueJS2, I would like to understand why in this example, the child is able to update the array that was declared in the parent and passed to the child via prop (one-way).
https://jsfiddle.net/ecgxykrt/
<script src="https://unpkg.com/vue"></script>
<div id="app">
<span>Parent value: {{ dataTest }}</span>
<test :datatest="dataTest" />
</div>
var test = {
props: ['datatest'],
mounted: function() {
this.datatest.push(10)
},
render: function() {}
}
new Vue({
el: '#app',
components: {
'test': test
},
data: function() {
return {
dataTest: []
}
}
})
Thanks in advance!
Vue prevents you from assigning to a prop. It does not prevent you from calling a prop's methods or modifying its elements or members, any of which can change the contents of the object. None of these things changes the value of the prop itself, which is a reference to an underlying structure.
A related issue is the fact that Vue cannot detect changes to Array elements or additions/deletions of Object members.
More here.
If you wanted to, you could avoid this by creating a shallow copy and assigning it to a new data item in the child.
https://jsfiddle.net/6xxba1fz/
var test = {
props: ['test'],
data: function() {
return {
myTest: this.test.slice()
}
},
mounted: function() {
this.myTest.push(10)
},
render: function() {}
}
new Vue({
el: '#app',
components: {
'test': test
},
data: function() {
return {
dataTest: []
}
}
})
Please avoid to using the some name for key and value
:datatest="dataTest" Wrong Way
:data-test="dataTest" Better Way (use Kabab case)
HTML
<div id="app">
<span>Parent value: {{ dataTest }}</span>
<test :data-test="dataTest" />
</div>
JS
var test = {
props: {
dataTest:{
type:Number
}
},
mounted: function() {
this.datatest.push(10)
},
render: function() {}
}
new Vue({
el: '#app',
components: {
'test': test
},
data: function() {
return {
dataTest: []
}
}
})
Result:
Parent value: []
Related
My Vue 2 fails to react when an entry is added to an object returned by data(). I can make it work other way but would like to know the underlying reason. See also JSfiddle
var app = new Vue ({
el: "#app",
data() {
return {
addedRows: {
'section1': {}
}
}
},
methods: {
addToList(chapter) {
this.addedRows.section1[chapter] =
this.addedRows.section1[chapter] || []
this.addedRows.section1[chapter].push({'T': 'XXX'})
console.log(this.addedRows)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<span #click="addToList('chapter1')">{{ addedRows }}</span>
</div>
I am trying to set a flag that can be a boolean value true or false based on certain conditions.
// main.js
new Vue({
el: `#app`,
render: h => h(App,{
props:{
todoID: this.dataset.id
}
})
})
my App script
export default {
name: 'App',
props: {
todoID: String,
},
data() {
return {
isDone: false // here is the initial flag
}
},
...
I created a mixin method
import Vue from "vue";
Vue.mixin({
methods: {
isCompleted() {
if (myconditions){
this.isDone = true; // I want to change the flag to true
}
},
},
});
in my template if I do {{isDone}} I always get false, how can I change this to be reactive so it can be changed based on the conditions?
Here is a demo I created: https://codesandbox.io/embed/vigorous-bell-p1itu?fontsize=14&hidenavigation=1&theme=dark
Vue.mixin({
methods: {
isCompleted(myconditions) {
console.log(myconditions);
console.log(typeof myconditions);
return this.isDone = myconditions; // I want to change the flag to true
},
},
});
new Vue({
el: '#vue-app',
data: {
isDone: false
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="vue-app">
<p> result = {{ isCompleted(true) }}</p>
</div>
</body>
I believe this is because I didn't see in your code where this isDone variable is being changed.
I made a demonstration with a function changing this variable through the same method done in your mixin.
example
I have a model from a backend where the property that normally contains an array of elements can be nullable. When this happens I'll init it to an empty array. However when doing that it seems to break my ability to update the array in the child component. Sample code can be found below.
Vue.component('Notes', {
template: '<div>{{items}}<ul><li v-for="item in items">{{ item.text }}</li></ul><button #click="add">Add</button></div>',
props: {
items: Array,
},
methods: {
add() {
console.log('added');
this.items.push({ text: "new item" });
}
}
});
new Vue({
el: "#demo",
data() { return { model: { } }},
created() { if(!this.model.items) this.model.items = []; }
});
<script src="https://unpkg.com/vue"></script>
<div id="demo">
<Notes :items="model.items" />
</div>
If data in the main component is model: { items : [] } everything works fine. But I don't have control over the backend data to guarantee that.
In your Notes component, you declare a model in the data, then, just underneath, you add an items[] if one doesn't exist already. This is not a good practice, and could be the cause of your problems. Vue needs to know about all the properties on objects it's watching. They need to be there when Vue first processes the object, or you need to add them with Vue.set().
You should emit an event to update the prop in the parent component
in child component :
this.$emit('add-item',{
text: "new item"
});
in parent template add a handler for the emitted event :
<Notes :items="model.items" #add-item="AddItem" />
Vue.component('Notes', {
template: '<div>{{items}}<ul><li v-for="item in items">{{ item.text }}</li></ul><button #click="add">Add</button></div>',
props: {
items: Array,
},
methods: {
add() {
console.log('added');
this.$emit('add-item', {
text: "new item"
});
}
}
});
new Vue({
el: "#demo",
data() {
return {
model: {
items: [] //init it here in order to avoid reactivity issues
}
}
},
methods: {
AddItem(item) {
this.model.items.push(item)
}
},
});
<script src="https://unpkg.com/vue"></script>
<div id="demo">
<Notes :items="model.items" #add-item="AddItem" />
</div>
I want to change the parent's prop's value from a child component. This works great in vuejs 1 but not in vue 2 (I want to use it in vue.js 2).
Here is a small example :
HTML
<div id="app">
<parent :content="{value:'hello parent'}"><</parent>
</div>
JavaScript
var parent = {
template: '<child :content="content"></child>',
props: ['content'],
};
var child = {
template: '<div>{{ content.value }}<button #click="change">change me</button></div>',
props: ['content'],
methods: {
change() {
this.content.value = "Value changed !";
}
}
};
Vue.component('child', child);
Vue.component('parent', parent);
new Vue({
el: '#app',
});
https://jsfiddle.net/f5gt94f2/
tl;dr: in vue2, you need to use the .sync modifier.
Create a local copy of the content prop in the parent's data (see reason here).
var parent = {
...
data() {
return {
localContent: this.content // creating a local copy, so we can mutate and react to it
}
}
};
Now, pass that localContent to the child, not content. And pass it using .sync so it can be updated:
var parent = {
template: '<div><child :content.sync="localContent"></child></div>',
... // ^^^^^^^^^^^^^^^^^^^^-- changed here
Now, in the child, don't assign to this.content.value, emit an update event instead:
var child = {
...
change() {
this.$emit('update:content', {value: "Value changed !"})
}
}
};
This event, with the new value, will be picked up by the parent and will update its localContent which also will, in consequence, update the child's content prop.
Final running code below.
var parent = {
template: '<div><child :content.sync="localContent"></child><br>At parent: {{ localContent }}</div>',
props: ['content'],
data() {
return {
localContent: this.content
}
}
};
var child = {
template: '<div>At child: {{ content.value }}<button #click="change">change me</button></div>',
props: ['content'],
methods: {
change() {
this.$emit('update:content', {value: "Value changed !"})
}
}
};
Vue.component('child', child);
Vue.component('parent', parent);
new Vue({
el: '#app'
});
<script src="https://unpkg.com/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<parent :content="{value:'hello parent'}"></parent>
</div>
You will have to use emit events for this
Parent:
<child :content="content" #updateParent="updateValue"></child>
methods: {
updateValue (value) {
// Your code here
}
}
Child:
props: ['content'],
methods: {
change () {
this.$emit('updateParent', value)
}
}
https://v2.vuejs.org/v2/guide/components.html#Custom-Events
This question is linked with Synchronise a props in Vuejs
The solution given as an answer works perfeclty. But in the case or the child component have a lot of data, the performances are killed because Vuejs rerender the component.
Here is the reproduced issue:
https://codepen.io/anon/pen/MvwRgo?editors=1011
Vue.component('myinput', {
template: '<div><input v-model="wrapperMyText"><div v-for="item in toto">{{item}}</div></div>',
props: ['text'],
data() {
return {
toto: new Array(100000)
}
},
computed: {
wrapperMyText: {
get() { return this.text;},
set(v) { this.$emit('update:text', v);}
}
}
})
const vm = new Vue({
el: '#app',
template: `
<div>
<myinput :text.sync="mytext"></myinput>
<p> {{mytext}} </p>
<button #click="myclick">OK</button>
</div>`,
data() {
return {
mytext: "Hello World!"
}
},
methods: {
myclick() {
this.mytext = 'Foobar';
}
}
});
How can I keep the same behavior without killing the performances ?