<template>
<span>{{time}}</span>
</template>
<script>
export default {
name: 'Timer',
data () {
return {
time: 0
}
},
mounted () {
setInterval(() => {
++this.time
}, 1000)
}
}
</script>
The parent
<template>
<div class="tetris">
<timer></timer>
</div>
</template>
<script>
import Timer from './Timer'
export default {
name: 'Game',
components: {
Timer: Timer
},
}
What is the best way to reset the timer here from the parent?
I want to avoid emitting events as this would result in all uses of the timer being reset. Is the best option to pass in a prop eg "start" "stop" "reset"?
Using a prop and watching it could be a good way to handle this if you're hoping to propagate events from the parent to the child. In general, communication of data between parent and child components should be done with props for parent-to-child communication and emitting events for child-to-parent communication.
There are, of course, additional libraries available for managing inter-component communication, but the above generally holds true for pure Vue.js code.
Yes there is a good way instead of increasing the timer in the child make it in the parent data and increment it in the parent and use props to pass it to the child and you can reset it in the parent when you like >> you can copy the code below and see the results
child code
<template>
<span>{{timer}}</span>
</template>
<script>
export default {
name: 'Timer',
data () {
return {
}
},
props:{timer:Number},
mounted() {
}
}
</script>
parent code
<script>
import Timer from './Timer'
export default {
name: 'Game',
data(){
return {
timerInit:0
},
components: {
Timer: Timer
},
mounted(){
setInterval(()=>{
++this.timerInit ;
console.log(this.timerInit);
},1000)
},
methods:{
clickHandler(){
this.timerInit = 0 ;
console.log(this.timerInit);
}
}
}
<template>
<div class="tetris">
<timer:timer="timerInit"/>
<button #click="clickHandler">reset</button>
</div>
</template>
Related
Hi i have a situation where i need to register multiple components at run time within a file.
Display.vue
<template>
<div>
<component v-if="currentComponent" :is="currentComponent">
</component>
</div>
</template>
<script>
import Vue from 'vue';
export default {
data(){
return{
currentComponent:null,
}
},
methods:{
load(e){
for(let key in e.components){
Vue.component(key,e.components[key]);
}
this.$nextTick(() =>{
this.currentComponent = e.components.container;
});
},
},
created(){
document.body.addEventListener('component-ready',this.load, false);
},
}
</script>
in my above file how i want my components to be loaded something like shown below:
components:{
container: component Object{},
header: component Object{},
body: component Object {},
footer: component Object {},
}
here is how i'm dispatching event to above file Display.vue
const event = new Event('component-ready');
event.components = {
container: component Object{},
header: component Object{},
body: component Object {},
footer: component Object {},
};
document.body.dispatchEvent(event);
Execution sequence:
event dispatch component-ready
eventlisterner in created of file Display.vue will call load method
Problem: in my current approach components are registered globally, that i want to avoid. i want to register all components to Display.vue file only
To locally register the components on the fly, you can copy the component definitions into this.$options.components:
export default {
methods: {
load(e) {
this.$options.components = e.components 👈
this.$nextTick(() => {
this.currentComponent = e.components.container
})
},
},
}
demo
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');
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");
}
}
}
I have an app in which I have a counter which shows a value, and a button that can increment that value.
I used simple state management from scratch as the docs suggest.
I can add counters to this list with an 'Add Counter' button so that I have multiple counters on the page.
Despite each instance of my counter component having a separate key in the parent component (as per the docs), each instance of the counter shares the same value:
How can I add a separate instance of the same component that has its own state?
Here is the code on webpackbin: http://www.webpackbin.com/41hjaNLXM
Code:
App.vue
<template>
<div id="app">
<counter v-for="n in state.countersAmount" :key="n"></counter>
<button v-on:click="addCounter">Add a Counter</button>
</div>
</template>
<script>
import Counter from './Counter.vue'
const store = {
state: {
countersAmount: 1
},
incrementCounters() {
++this.state.countersAmount
}
}
export default {
data() {
return {
state: store.state
}
},
methods: {
addCounter() {
store.incrementCounters()
}
},
components: {
Counter
}
}
</script>
Counter.vue
<template>
<div>
<h1>{{state.counterValue}}</h1>
<button v-on:click="increment">+</button>
</div>
</template>
<script>
const store = {
state: {
counterValue: 0,
},
increment() {
++this.state.counterValue
}
}
export default {
data() {
return {
state: store.state
}
},
methods: {
increment() {
store.increment()
}
}
}
</script>
You're using the same state for every Counter instance.
const store = {
state: {
counterValue: 0,
},
increment() {
++this.state.counterValue
}
}
The code above will be executed only once, and every instance of this component will share this state.
To change this, just return a new object as the initial state, like so:
<template>
<div>
<h1>{{counterValue}}</h1>
<button v-on:click="increment">+</button>
</div>
</template>
<script>
export default {
data() {
return {
counterValue: 0
}
},
methods: {
increment() {
++this.counterValue;
}
}
}
</script>
The Simple State Management from Scratch you linked, is for shared state between components, as you can see in the picture:
You are always returning the same instance of the component. Instead, you should return a new instance.
Let's say I have a main Vue instance that has child components. Is there a way of calling a method belonging to one of these components from outside the Vue instance entirely?
Here is an example:
var vm = new Vue({
el: '#app',
components: {
'my-component': {
template: '#my-template',
data: function() {
return {
count: 1,
};
},
methods: {
increaseCount: function() {
this.count++;
}
}
},
}
});
$('#external-button').click(function()
{
vm['my-component'].increaseCount(); // This doesn't work
});
<script src="http://vuejs.org/js/vue.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app">
<my-component></my-component>
<br>
<button id="external-button">External Button</button>
</div>
<template id="my-template">
<div style="border: 1px solid; padding: 5px;">
<p>A counter: {{ count }}</p>
<button #click="increaseCount">Internal Button</button>
</div>
</template>
So when I click the internal button, the increaseCount() method is bound to its click event so it gets called. There is no way to bind the event to the external button, whose click event I am listening for with jQuery, so I'll need some other way to call increaseCount.
EDIT
It seems this works:
vm.$children[0].increaseCount();
However, this is not a good solution because I am referencing the component by its index in the children array, and with many components this is unlikely to stay constant and the code is less readable.
In the end I opted for using Vue's ref directive. This allows a component to be referenced from the parent for direct access.
E.g.
Have a component registered on my parent instance:
var vm = new Vue({
el: '#app',
components: { 'my-component': myComponent }
});
Render the component in template/html with a reference:
<my-component ref="foo"></my-component>
Now, elsewhere I can access the component externally
<script>
vm.$refs.foo.doSomething(); //assuming my component has a doSomething() method
</script>
See this fiddle for an example: https://jsfiddle.net/0zefx8o6/
(old example using Vue 1: https://jsfiddle.net/6v7y6msr/)
Edit for Vue3 - Composition API
The child-component has to return the function in setup you want to use in the parent-component otherwise the function is not available to the parent.
Note: <sript setup> doc is not affacted, because it provides all the functions and variables to the template by default.
You can set ref for child components then in parent can call via $refs:
Add ref to child component:
<my-component ref="childref"></my-component>
Add click event to parent:
<button id="external-button" #click="$refs.childref.increaseCount()">External Button</button>
var vm = new Vue({
el: '#app',
components: {
'my-component': {
template: '#my-template',
data: function() {
return {
count: 1,
};
},
methods: {
increaseCount: function() {
this.count++;
}
}
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-component ref="childref"></my-component>
<button id="external-button" #click="$refs.childref.increaseCount()">External Button</button>
</div>
<template id="my-template">
<div style="border: 1px solid; padding: 2px;" ref="childref">
<p>A counter: {{ count }}</p>
<button #click="increaseCount">Internal Button</button>
</div>
</template>
For Vue2 this applies:
var bus = new Vue()
// in component A's method
bus.$emit('id-selected', 1)
// in component B's created hook
bus.$on('id-selected', function (id) {
// ...
})
See here for the Vue docs.
And here is more detail on how to set up this event bus exactly.
If you'd like more info on when to use properties, events and/ or centralized state management see this article.
See below comment of Thomas regarding Vue 3.
You can use Vue event system
vm.$broadcast('event-name', args)
and
vm.$on('event-name', function())
Here is the fiddle:
http://jsfiddle.net/hfalucas/wc1gg5v4/59/
A slightly different (simpler) version of the accepted answer:
Have a component registered on the parent instance:
export default {
components: { 'my-component': myComponent }
}
Render the component in template/html with a reference:
<my-component ref="foo"></my-component>
Access the component method:
<script>
this.$refs.foo.doSomething();
</script>
Say you have a child_method() in the child component:
export default {
methods: {
child_method () {
console.log('I got clicked')
}
}
}
Now you want to execute the child_method from parent component:
<template>
<div>
<button #click="exec">Execute child component</button>
<child-cmp ref="child"></child_cmp> <!-- note the ref="child" here -->
</div>
</template>
export default {
methods: {
exec () { //accessing the child component instance through $refs
this.$refs.child.child_method() //execute the method belongs to the child component
}
}
}
If you want to execute a parent component method from child component:
this.$parent.name_of_method()
NOTE: It is not recommended to access the child and parent component like this.
Instead as best practice use Props & Events for parent-child communication.
If you want communication between components surely use vuex or event bus
Please read this very helpful article
This is a simple way to access a component's methods from other component
// This is external shared (reusable) component, so you can call its methods from other components
export default {
name: 'SharedBase',
methods: {
fetchLocalData: function(module, page){
// .....fetches some data
return { jsonData }
}
}
}
// This is your component where you can call SharedBased component's method(s)
import SharedBase from '[your path to component]';
var sections = [];
export default {
name: 'History',
created: function(){
this.sections = SharedBase.methods['fetchLocalData']('intro', 'history');
}
}
Using Vue 3:
const app = createApp({})
// register an options object
app.component('my-component', {
/* ... */
})
....
// retrieve a registered component
const MyComponent = app.component('my-component')
MyComponent.methods.greet();
https://v3.vuejs.org/api/application-api.html#component
Here is a simple one
this.$children[indexOfComponent].childsMethodName();
I am not sure is it the right way but this one works for me.
First import the component which contains the method you want to call in your component
import myComponent from './MyComponent'
and then call any method of MyCompenent
myComponent.methods.doSomething()
Declare your function in a component like this:
export default {
mounted () {
this.$root.$on('component1', () => {
// do your logic here :D
});
}
};
and call it from any page like this:
this.$root.$emit("component1");
If you're using Vue 3 with <script setup> sugar, note that internal bindings of a component are closed (not visible from outside the component) and you must use defineExpose(see docs) to make them visible from outside. Something like this:
<script setup lang="ts">
const method1 = () => { ... };
const method2 = () => { ... };
defineExpose({
method1,
method2,
});
</script>
Since
Components using are closed by default
Sometimes you want to keep these things contained within your component. Depending on DOM state (the elements you're listening on must exist in DOM when your Vue component is instantiated), you can listen to events on elements outside of your component from within your Vue component. Let's say there is an element outside of your component, and when the user clicks it, you want your component to respond.
In html you have:
Launch the component
...
<my-component></my-component>
In your Vue component:
methods() {
doSomething() {
// do something
}
},
created() {
document.getElementById('outsideLink').addEventListener('click', evt =>
{
this.doSomething();
});
}
I have used a very simple solution. I have included a HTML element, that calls the method, in my Vue Component that I select, using Vanilla JS, and I trigger click!
In the Vue Component, I have included something like the following:
<span data-id="btnReload" #click="fetchTaskList()"><i class="fa fa-refresh"></i></span>
That I use using Vanilla JS:
const btnReload = document.querySelector('[data-id="btnReload"]');
btnReload.click();