In Vue2 I'm trying to access child components' data and then put into parent component's data without triggering an event. In the following example I want to save count:20 into parent component, please tell me if there's any mistake, thanks!
Child Component
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
};
</script>
Parent Component
<template>
<div>
<child ref="child1"></child>
{{count}}
</div>
</template>
<script> import child from './child.vue'
export default {
components: {
child
},
data() {
return{
count:this.$refs.child1.count
}
},
}
</script>
warn message in VScode
Property 'count' does not exist on type 'Vue | Element | Vue[] | Element[]'.
Property 'count' does not exist on type 'Vue'.
warn message in browser
[Vue warn]: Error in data(): "TypeError: undefined is not an object (evaluating 'this.$refs.child1')"
Let me preface with I would recommend using the Vue framework as intended. So passing data from a child to the parent should be done with $emit or using a vuex store for centralized state management.
With that out of the way you will want to wait until the parent component is mounted to set the count data attribute.
Child
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
};
</script>
Parent
<template>
<div>
<child ref="child1"></child>
{{ count }}
</div>
</template>
<script>
import Child from "./components/Child";
export default {
components: {
Child
},
data() {
return{
count: 0
}
},
mounted () {
this.count = this.$refs.child1.count
}
};
</script>
This will work, however it WILL NOT BE reactive. This can all be greatly simplified AND made reactive with the following changes:
Child
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
watch: {
count (currentValue) {
this.$emit('update', currentValue);
}
},
beforeMount () {
this.$emit('update', this.count)
}
};
</script>
Parent
<template>
<div>
<child #update="count = $event"></child>
{{ count }}
</div>
</template>
<script>
import Child from "./components/Child";
export default {
components: {
Child
},
data() {
return{
count: 0
}
}
};
</script>
Quick link to show a working example: https://codesandbox.io/s/interesting-kalam-et0b3?file=/src/App.vue
I have 2 components, both sharing a common parent. I want to be able to pass data from one child component to the other. (I am using vueJS 2)
The communication should be handled by the parent component as follows:
Child 1 sends the data to parent
this.$emit('myVarChanged', this.myVar);
The parent listens to the emits from the last, saves the new value, and pass it to the second child:
<ChildOneComponent #myVarChanged="updateMyVar" />
data() { return { myVar:null } }
methods: {
updateMyVar(val){
this.myVar = val;
}
}
<ChildTwoComponent :myVar="myVar" />
Child 2 gets the new value as props:
props: ['myVar']
Demo:
const child1 = Vue.component('child1', {
template: '#child1',
data() { return { count:0 } },
methods: {
updateParent() { this.$emit('myvarchanged', this.count++); }
}
});
const child2 = Vue.component('child2', {
template: '#child2',
props: ['myvar']
});
new Vue({
el:"#app",
components: { child1, child2 },
data() { return { myvar:null } },
methods: {
updateMyVar(val) { this.myvar = val; }
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template id="child1">
<div>
<h1>Child 1</h1>
<button #click="updateParent">Click to send to child 2</button>
</div>
</template>
<template id="child2">
<div>
<h1>Child 2</h1>
<p>Prop from child 1: {{myvar}}</p>
</div>
</template>
<div id="app">
<div><child1 #myvarchanged="updateMyVar" /></div>
<div><child2 :myvar="myvar" /></div>
</div>
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>
Question
In VueJS 2 how do you show some HTML if a Function prop was passed to the component.
Example
<template>
<div v-if="backBtn" #click="$emit('backBtn')">Back Button</div>
</template>
<script>
export default {
props: {
backBtn: Function
}
}
</script>
I can do this by passing a separate prop to key the v-if off of but I'm trying to do this will the one prop.
I created a Fiddle for this issue here
that should work,
you can add more definition with !== undefined
<template>
<div v-if="backBtn !== undefined" #click="$emit('backBtn')">Back Button</div>
</template>
<script>
export default {
props: {
backBtn: {
type: Function,
},
}
}
</script>
but as mentioned, that should work already, so you error may be somewhere else.
after seeing your code, I see what the issue is. it's a case issue
use :back-btn instead of :backBtn
this happens only if you're using vue runtime only (without the compilation)
read more here:
https://v2.vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only
you can solve it also by passing the function only
https://jsfiddle.net/rz6hyd7b/7/
Vue.component('my-btn', {
props: {
backbtn: {
type: Function
}
},
template: `
<div>
<div v-if="backbtn" #click="backbtn">Back Button</div>
</div>
`
})
var vm = new Vue({
el: '#app',
components: 'my-btn',
methods: {
btnClicked: function(){
console.log('adsf')
}
},
template: `
<div>
Show Btn => <my-btn :backbtn="btnClicked"></my-btn>
</br>
Hidden Btn => <my-btn></my-btn>
</div>
`
});
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");
}
}
}