How to call function on child component on parent events - javascript

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");
}
}
}

Related

Sibling component communication not working in vue

I am trying to send this.TC from typing.js to ending-page.js which are sibling components. Emits and event hubs not working. But emit from typing.js to parent works as I want. (There will be only one more call in this app, so i don't want use Vuex if it isnt necessary for this - i want to do it with simple emits ) Here's my code:
Parent:
<template>
<div id = "app">
<typing v-if = "DynamicComponent === 'typing'" />
<ending_page v-else-if = "DynamicComponent === 'ending_page'" />
</div>
</template>
<script>
/* Importing siblings components to parent component */
import typing from './components/typing/index.vue'
import ending_page from './components/ending-page/index.vue'
export default {
name: 'app',
components: {
typing,
ending_page
},
data() {
return {
DynamicComponent: "typing",
};
},
methods: {
updateDynamicComponent: function(evt, data){
this.DynamicComponent = evt;
},
},
};
</script>
typing.js:
import { eventBus } from "../../main";
export default {
name: 'app',
components: {
},
data() {
return {
/* Text what is in input. If you write this.input = "sometext" input text will change (It just works from JS to HTML and from HTML to JS) */
input: "",
/* Object of TypingCore.js */
TC: "somedata",
/* Timer obejct */
timer: null,
is_started: false,
style_preferences: null,
};
},
ICallThisFunctionWhenIWantToEmitSomething: function(evt) {
/* Sending data to ending_page component */
this.$root.$emit('eventname', 'somedata');
/* Calling parent to ChangeDynamicComponent && sending TC.data what will be given to ending_page (I think it looks better with one syntax here) */
this.$emit('myEvent', 'ending_page', this.TC.data);
}
},
};
ending-page.js:
import { eventBus } from "../../main";
export default {
name: 'ending-page',
components: {},
data () {
return {
data: "nothing",
}
},
computed: {
},
props: {
},
methods: {
},
/* I know arrow functions etc but i was trying everyting */
created: function () {
this.$root.$on('eventname', function (data) {
console.log(data)
this.title = data
this.$nextTick()
})
}
}
It is an example of how to share data between siblings components.
Children components emits events to parent. Parent components send data to children.
So, the parent has the property title shared between the children. When typing emits
the input event the directive v-modelcapture it an set the value on parent.
Ref:
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
https://benjaminlistwon.com/blog/data-flow-in-vue-and-vuex/
Vue.component('typing', {
props: {
value: ''
},
template: '<button #click="emit">Click to change</button>',
methods: {
emit() {
this.$emit('input', `changed on ${Date.now()}`);
}
}
});
Vue.component('ending-page', {
props: {
title: ''
},
template: '<div>{{ title }}</div>',
});
var app = new Vue({
el: '#app',
data() {
return {
title: 'unchanged',
};
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<typing v-model="title"></typing>
<ending-page :title="title"></ending-page>
</div>
One can try communication using vuex,
the data you want to share make it on this.$store.state or if recalling for functions use mutation(sync functions) and actions(async functions)
https://vuex.vuejs.org/
I like what Jeffrey Way suggested once, just create a global events object (which accidentally can be another Vue instance) and then use that as an event bus for any global communication.
window.eventBus = new Vue();
// in components that emit:
eventBus.$emit('event', data);
// in components that listen
eventBus.$on('event');

Hook=componentUpdated of Vue directive not triggered

I just met one issue, if one component only update its own data, it will not trigger the hook=componentUpdated of the directive at the parent component.
As Vue official Guide said:
componentUpdated: called after the containing component’s VNode and
the VNodes of its children have updated.
It seems componentUpdated should be triggered.
Did I do something wrong? or misunderstand something?
At below demo, hit Click Me! button then you will see componentUpdated is not called.
But when click change data (execute similar behavior with click me!, the difference is it changes the data at parent component), it will trigger correctly.
Many thanks for any.
Vue.config.productionTip = false
Vue.component('child', {
template: `<div>{{point}}
<span style="background-color:gray;font-weight:bold;color:red">
-{{mytest}}
</span>
<button #click="plusOne()">Click me!</button>
</div>`,
props: ['point'],
data(){
return {
mytest: 1
}
},
updated: function () {
console.log('updated component=child')
},
methods: {
plusOne() {
this.mytest += 1
}
}
})
let vMyDirective = {}
vMyDirective.install = function install (Vue) {
Vue.directive('my-directive', {
inserted: function () {
console.log('!!!directive for inserted')
},
bind: function bind (el, binding, vnode) {
console.log('!!!directive for bind')
},
componentUpdated: function componentUpdated (el, binding, vnode) {
console.log('!!!directive for component updated')
},
update: function () {
console.log('!!!directive for update')
}
})
}
Vue.use(vMyDirective)
new Vue({
el: '#app',
data() {
return {
testValues: ['label a', 'label b'],
testIndex: 1
}
},
methods:{
pushArray: function() {
this.testValues.push('label c')
},
changeData: function () {
this.testIndex += 1
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<button v-on:click="pushArray()">Add one Child!!!</button>
<button v-on:click="changeData()">Change Data - {{testIndex}}</button>
<div v-my-directive>
<child v-for="(item, index) in testValues" :key="index" :point="item"></child>
</div>
</div>
Based on Vue Team Feedback, it is not one issue on the hook=componentUpdated, it is my misunderstanding on the words.
For the prerequisite of the hook=comopnentUpdated is triggered, it is the VNode which the directive binds to already changed. That means if only child VNode changes, Vue will not catch it probably like what #Jacob Goh said in the comments (only flows one way).
So componentUpdated doesn't means it will detect child components are updated or not, it only means when will be triggered.

Vue reactive props on programmatic component

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>

VueJS - Props are not updated in child component by changes in parent

Props are not updated when I change them in parent component
Parent component:
I have controlData value as defaul value for child component prop control which is equal 2 and I can see that value when I run my app first time
data() {
return {
controlData: 2
}
}
In ready() I need to load data from back-end and to set that value to child component prop control equal to the data from back-end.
But lets say that now I just want to change control (value in child) when parent component is ready. So I made this in parent component:
ready() {
this.controlData = 55;
}
Then I use v-bind to send that value in child when controlData is changed
<child-component :control="controlData"></child-componenet>
Child component:
I have this in my child component
export default Bar.extend({
props: ["control"],
ready() {
console.log(this.control); // I see only default value "2" not "55" - but I expect to see "55" because I changed that value in ready() of parent
}
})
I added also watch: {} to look for changes of props but I can't see the changes
watch: {
control() {
console.log("Control is changed"); // I don't see this message when I change controlData value in parent and then by v-bind:control="controlData" i send that data in child component
}
}
The code you have posted should update the child prop if correctly implemented.
One thing to note, the child's ready() hook will be executed BEFORE the parent ready() hook. So you should see the console log the following:
2
Control is changed
This is working for me using Vue 1.0.28:
https://codepen.io/camaulay/pen/wejpPa?editors=1011
JS:
var child = Vue.extend({
template: '<div>Child data: {{ control }}</div>',
props: ['control'],
ready () {
console.log(this.control);
},
watch: {
control () {
console.log("Control is changed")
console.log(this.control)
}
}
})
var app = new Vue({
el: '#app',
components: {
'child': child
},
data () {
return {
controlData: 2
}
},
ready () {
this.controlData = 55
}
})
HTML:
<div id="app">
<child :control="controlData"></child>
<button #click="controlData++">Increment parent data</button>
</div>

How to access a component property from App.vue

I used vue-loader to help me install vue and webpack
I have a file called App.vue
In App.vue I added a component called widget. If I clicked some button there's a function that set the btnClicked = true hence the widget appears
<widget v-show="btnClicked"></widget>
but I also want that function to access the widgetShowMe, it's a property in my component.
I want the function activated in my App.vue to also set widgetShowMe = true
I tried this but it didn't work
methods:{
btnClickedFunc () {
this.btnClicked = true;
Widget.widgetShowMe = true;
}
}
Accessing child component's data in parent component in vuejs
If you have a parent component called parent and child component called child, you can communicate between each other using props and events.
props: Facilitates communication from parent to child.
events: Can be used to pass data in a child component to the parent component.
For this question we require events and will use v-model to make the child component usable everywhere with much less setup.
Vue.component('counter', {
template: `<div><button #click='add'>+1</button>
<button #click='sub'>-1</button>
<div>this is inside the child component: {{ result }}</div></div>`,
data () {
return {
result: 0
}
},
props: ['value'],
methods: {
emitResult () {
this.$emit('input', this.result)
},
add () {
this.result += 1
this.emitResult()
},
sub () {
this.result -= 1
this.emitResult()
}
}
})
new Vue({
el: '#demo',
data () {
return {
resultFromChild: null
}
}
})
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id='demo'>
<counter v-model='resultFromChild'></counter>
This is in parent component {{ resultFromChild }}
</div>
Custom component with v-model
This needs two requirements.
You have a prop on the child component with the name value.
props: ['value'], // this part in the child component snippet
You emit the event input with the value.
this.$emit('input', this.result) // this part in the child component snippet
All you need to think of is, when to emit the event with the value of widgetShowMe, and your app.vue can easily capture the value inside your widget.

Categories