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
Related
I know use $emit to pass data from child to parent components on VueJS but I want get that value on javascript function. My scenario is:
Parent
created () {
this.$on('getValue', function (params) {
console.log('PARAMS: ' + params)
})
},
Child
methods: {
checkBoxChanged (index) {
this.$emit('getValue', 'some value')
},
}
But it ins't works. Using html I can set on Parent using something like: (I'VE REASONS TO CAN'T DO IT!)
<template>
<div>
<h1>{{ message }}</h1>
<child v-on:listenerChild="listenerChild"/>
</div>
</template>
But I need do this using just javascript.
That's how you can pass data from child to parent:
Vue.component('child-component', {
template:
`<button type="button" #click="emitClick">
Click to emit event to parent component
</button>`,
methods: {
emitClick() {
this.$emit('buttonClicked', 'Magic from child component!');
},
},
});
Vue.component('parent-component', {
template:
`<div>
<div>Child data is: {{ childData }}</div>
<child-component #buttonClicked="handleClick" />
</div>`,
data() {
return {
childData: 'empty',
};
},
methods: {
handleClick(data) {
this.childData = data;
},
},
});
new Vue({
el: '#app',
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<parent-component />
</div>
Given a component:
Vue.component('my-comp', {
props: ['input'],
watch: { input: function(){...} },
});
What is the programmatic method for the following?
<my-comp :input="map[key]"></my-comp> map[key] change triggers watch
I have tried:
new (Vue.component('my-comp'))({
propsData: { input:map[key] }, // map[key] change doesn't trigger watch
});
The context for this is inserting zero-to-many components into markdown-generated HTML. I call .$mount() for each component, and move its node with a native DOM replaceChild() call when markdown is re-rendered. See also Vue components in user-defined markdown
If prop input is a primitive value, we have to manipulate the component with child.$props.input = x as Roy J suggests, but in this case we need input = map[key]. Hence this solution:
Vue.component('my-comp', {
props: ['map','key'],
computed: { input: function() { return this.map[this.key] } },
watch: { input: function(a, b) {...} }, // triggered on map[key] change
});
new (Vue.component('my-comp'))({
propsData: { map:theMap, key:theKey }, // theMap must be reactive
});
A render function is the programmatic means of creating and inserting a component. Using new with propsData is primarily for unit testing, where the component will not necessarily have a Vue instance as a parent.
$mount doesn't establish a parent-child relationship, it just mounts the component free-standing to the DOM. You will need to set up the parent-child props management.
Vue.component('my-comp', {
template: '<div>{{ input }}</div>',
props: ['input']
});
new Vue({
el: '#app',
data: {
thingy: 5,
child: null
},
created() {
this.child = new(Vue.component('my-comp'))({
propsData: {
input: this.thingy
}
});
this.$watch('thingy', (newValue) => this.child.$props.input = newValue);
setInterval(() => ++this.thingy, 2000);
},
mounted() {
this.child.$mount(this.$el);
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div>
I want to $emit some data to all child components in vue.js 2 and this is my code :
Root Component
const app = new Vue({
created: function(){
// here is some promise and after it done the below line runs
this.$emit('foo', 'bar');
}
});
Child Component
<template></template>
<script>
export default {
created: function() {
this.$on('foo', function(data) {
console.log(data);
});
}
}
<script>
It's not working.
Is there any way to broadcast some data to all child components from root?
Vuejs is bottom up approach, means child component is compiled first and ask for this.$on so broadcasting will not work here with $emit-$on
Use props in child component to watch root data changes, giving this example where child1 and child2 having same root component data named name
var child1 = Vue.extend({
template: "<div>Child Component1 : Niklesh : {{name}} <div v-if='loading'>Loading...</div></div>",
props: ['name','loading']
});
var child2 = Vue.extend({
template: "<div>Child Component1 : Rishi : {{name}} <div v-if='loading'>Loading...</div></div>",
props: ['name','loading']
});
var app = new Vue({
el: "#vue-instance",
data: {
name: "...",
loading: true
},
mounted() {
var vm = this;
setTimeout(function() {
vm.name = "Raut";
vm.loading = false;
}, 1000);
},
components: {
child1,
child2
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.1/vue.js"></script>
<div id="vue-instance">
<child1 :name="name" :loading="loading"></child1>
<child2 :name="name" :loading="loading"></child2>
</div>
Use another Vue instance as an Event bus
Code Pen Sample
<div id="app">
<child></child>
</div>
var bus = new Vue()
Vue.component('child', {
data() {
return {
message: 'waiting for event...'
}
},
template: '<div>{{ message }}</div>',
created: function() {
bus.$on('foo', data => {
this.message = data;
})
}
})
var app = new Vue({
el: '#app',
created: function() {
setTimeout(()=> {
bus.$emit('foo', 'bar')
}, 1000)
}
})
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: []
Context
In Vue 2.0 the documentation and others clearly indicate that communication from parent to child happens via props.
Question
How does a parent tell its child an event has happened via props?
Should I just watch a prop called event? That doesn't feel right, nor do alternatives ($emit/$on is for child to parent, and a hub model is for distant elements).
Example
I have a parent container and it needs to tell its child container that it's okay to engage certain actions on an API. I need to be able to trigger functions.
Vue 3 Composition API
Create a ref for the child component, assign it in the template, and use the <ref>.value to call the child component directly.
<script setup>
import {ref} from 'vue';
const childComponentRef = ref(null);
function click() {
// `childComponentRef.value` accesses the component instance
childComponentRef.value.doSomething(2.0);
}
</script>
<template>
<div>
<child-component ref="childComponentRef" />
<button #click="click">Click me</button>
</div>
</template>
Couple things to note-
If your child component is using <script setup>, you'll need to declare public methods (e.g. doSomething above) using defineExpose.
If you're using Typescript, details of how to type annotate this are here.
Vue 3 Options API / Vue 2
Give the child component a ref and use $refs to call a method on the child component directly.
html:
<div id="app">
<child-component ref="childComponent"></child-component>
<button #click="click">Click</button>
</div>
javascript:
var ChildComponent = {
template: '<div>{{value}}</div>',
data: function () {
return {
value: 0
};
},
methods: {
setValue: function(value) {
this.value = value;
}
}
}
new Vue({
el: '#app',
components: {
'child-component': ChildComponent
},
methods: {
click: function() {
this.$refs.childComponent.setValue(2.0);
}
}
})
For more info, see Vue 3 docs on component refs or Vue 2 documentation on refs.
What you are describing is a change of state in the parent. You pass that to the child via a prop. As you suggested, you would watch that prop. When the child takes action, it notifies the parent via an emit, and the parent might then change the state again.
var Child = {
template: '<div>{{counter}}</div>',
props: ['canI'],
data: function () {
return {
counter: 0
};
},
watch: {
canI: function () {
if (this.canI) {
++this.counter;
this.$emit('increment');
}
}
}
}
new Vue({
el: '#app',
components: {
'my-component': Child
},
data: {
childState: false
},
methods: {
permitChild: function () {
this.childState = true;
},
lockChild: function () {
this.childState = false;
}
}
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
<div id="app">
<my-component :can-I="childState" v-on:increment="lockChild"></my-component>
<button #click="permitChild">Go</button>
</div>
If you truly want to pass events to a child, you can do that by creating a bus (which is just a Vue instance) and passing it to the child as a prop.
You can use $emit and $on. Using #RoyJ code:
html:
<div id="app">
<my-component></my-component>
<button #click="click">Click</button>
</div>
javascript:
var Child = {
template: '<div>{{value}}</div>',
data: function () {
return {
value: 0
};
},
methods: {
setValue: function(value) {
this.value = value;
}
},
created: function() {
this.$parent.$on('update', this.setValue);
}
}
new Vue({
el: '#app',
components: {
'my-component': Child
},
methods: {
click: function() {
this.$emit('update', 7);
}
}
})
Running example: https://jsfiddle.net/rjurado/m2spy60r/1/
A simple decoupled way to call methods on child components is by emitting a handler from the child and then invoking it from parent.
var Child = {
template: '<div>{{value}}</div>',
data: function () {
return {
value: 0
};
},
methods: {
setValue(value) {
this.value = value;
}
},
created() {
this.$emit('handler', this.setValue);
}
}
new Vue({
el: '#app',
components: {
'my-component': Child
},
methods: {
setValueHandler(fn) {
this.setter = fn
},
click() {
this.setter(70)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<my-component #handler="setValueHandler"></my-component>
<button #click="click">Click</button>
</div>
The parent keeps track of the child handler functions and calls whenever necessary.
Did not like the event-bus approach using $on bindings in the child during create. Why? Subsequent create calls (I'm using vue-router) bind the message handler more than once--leading to multiple responses per message.
The orthodox solution of passing props down from parent to child and putting a property watcher in the child worked a little better. Only problem being that the child can only act on a value transition. Passing the same message multiple times needs some kind of bookkeeping to force a transition so the child can pick up the change.
I've found that if I wrap the message in an array, it will always trigger the child watcher--even if the value remains the same.
Parent:
{
data: function() {
msgChild: null,
},
methods: {
mMessageDoIt: function() {
this.msgChild = ['doIt'];
}
}
...
}
Child:
{
props: ['msgChild'],
watch: {
'msgChild': function(arMsg) {
console.log(arMsg[0]);
}
}
}
HTML:
<parent>
<child v-bind="{ 'msgChild': msgChild }"></child>
</parent>
The below example is self explainatory. where refs and events can be used to call function from and to parent and child.
// PARENT
<template>
<parent>
<child
#onChange="childCallBack"
ref="childRef"
:data="moduleData"
/>
<button #click="callChild">Call Method in child</button>
</parent>
</template>
<script>
export default {
methods: {
callChild() {
this.$refs.childRef.childMethod('Hi from parent');
},
childCallBack(message) {
console.log('message from child', message);
}
}
};
</script>
// CHILD
<template>
<child>
<button #click="callParent">Call Parent</button>
</child>
</template>
<script>
export default {
methods: {
callParent() {
this.$emit('onChange', 'hi from child');
},
childMethod(message) {
console.log('message from parent', message);
}
}
}
</script>
If you have time, use Vuex store for watching variables (aka state) or trigger (aka dispatch) an action directly.
Calling child component in parent
<component :is="my_component" ref="my_comp"></component>
<v-btn #click="$refs.my_comp.alertme"></v-btn>
in Child component
mycomp.vue
methods:{
alertme(){
alert("alert")
}
}
I think we should to have a consideration about the necessity of parent to use the child’s methods.In fact,parents needn’t to concern the method of child,but can treat the child component as a FSA(finite state machine).Parents component to control the state of child component.So the solution to watch the status change or just use the compute function is enough
you can use key to reload child component using key
<component :is="child1" :filter="filter" :key="componentKey"></component>
If you want to reload component with new filter, if button click filter the child component
reloadData() {
this.filter = ['filter1','filter2']
this.componentKey += 1;
},
and use the filter to trigger the function
You can simulate sending event to child by toggling a boolean prop in parent.
Parent code :
...
<child :event="event">
...
export default {
data() {
event: false
},
methods: {
simulateEmitEventToChild() {
this.event = !this.event;
},
handleExample() {
this.simulateEmitEventToChild();
}
}
}
Child code :
export default {
props: {
event: {
type: Boolean
}
},
watch: {
event: function(value) {
console.log("parent event");
}
}
}