As of vue3, the $on method is no longer supported.
To add an event listener I have to write it on the element itself.
<transition name="fade" #after-enter="afterEnter">
<div>foobar</div>
</transition>
in the above sample, the afterEnter function have to be exposed to the component's context, but I want it to not be exposed to the component's context.
In vue2, we can just call the $on('after-enter', afterEnter). But how about in vue3 to do this?
I believe you might be confused. Running this code will NOT add the event listener to attrs in context. It only does this when you set an event on an instance of a component. For example:
<template>
<button>
Toggle
</button>
</template>
<script>
export default {
name: 'MyButton',
setup(props, { attrs }) {
console.log(attrs); // Proxy {__vInternal: 1, onClick: ƒ}
},
}
</script>
<my-button #click="handleClick"></my-button>
If you do NOT want the event to appear in attrs in this case, you must declared it with emits.
<template>
<button>
Toggle
</button>
</template>
<script>
export default {
name: 'MyButton',
emits: ['click'], // declare here
setup(props, { attrs }) {
console.log(attrs); // Proxy {__vInternal: 1} the event listener is gone
},
}
</script>
<my-button #click="handleClick"></my-button>
Alternatively, if you really want to use $on, you can use a external library like mitt or tiny-emitter to utilize the same API from Vue 2.
Here's some resources if that doesn't answer your question:
https://v3-migration.vuejs.org/breaking-changes/emits-option.html#overview
https://v3-migration.vuejs.org/breaking-changes/events-api.html
You should return afterEnter listener from the setup function.
<script>
export default {
setup() {
return { afterEnter: () => console.log('after enter hook') }
}
}
</script>
From my parent component, I need to trigger a child component's portion of code containing a Firebase query. Note that in the child component, that code is triggered within the mounted() hook (but in order to execute it from the parent component, I suppose I will have to move that code to a method or something).
What is the recommended way to achieve such a thing?
Here's my parent component code - simplified for clarity:
<template>
<div>
<nav>
<v-btn #click="viewNextWeek"></v-btn>
</nav>
<project-row :mon="mon"></new-row-form>
</div>
</template>
<script>
import ProjectRow from './ProjectRow.vue';
import store from '../store';
import moment from 'moment';
export default {
name: 'home',
components: {
ProjectRow
},
data() {
return {
mon: moment().startOf('isoWeek')
}
},
methods: {
viewNextWeek: function() {
this.mon = moment().startOf('isoWeek').add(7, 'days');
}
}
}
</script>
Child component - simplified too:
<template>
<div>
<input v-model="monTime">
</div>
</template>
<script>
import { db } from '../firebase';
export default {
props: [
'mon'
],
mounted() {
var timesRef = db.collection('times');
var timesWeek = timesRef.where('date', '==', this.mon);
timesWeek.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
this.monTime = doc.data().time;
});
})
.catch(function(error) {
console.log('Error getting documents: ', error);
});
}
}
</script>
to achieve that you should use event bus communication between these two components. first, you should create a global event bus that can be a central event management and will give you the possibility to access any event from any component within your vuejs app. in your case here, you should emit an event from your parent component within viewNextWeek method and catch it within mounted in child component. so every time your mon value change within your parent component, will trigger an event to the child component to update mon within it.
for more detail for how to use global event bus, read this article global event bus article
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();
I am building a form framework in vue. I have components for each field type. Each field type component uses this.$emit to communicate changes with the parent component.
I am able to trigger events in the parent component using v-on directives as follows:
<template>
<div v-if="fieldsLoaded">
<form-select :field="fields.title" v-on:updated="validate" ></form-select>
<form-input :field="fields.first_name" v-on:updated="validate" ></form-input>
</div>
</template>
However, I don't want to have to manually specify that every component should trigger the validate method individually.
How can I have the parent component listen for the updated emit across all its child components?
Edit: I'm looking for something like the below, though $on only catches emits that occur within the same component, rather than its children
created: function(){
this.$on('updated',validate)
}
The best way is to use event bus or even better in my opinion vuex.
For the first case take a look here
For the second here
With event bus you can emit an event, and listen to that event whenever you want(at parent,child even in the same component)
Vuex It serves as a centralized store for all the components in an application and you can have properties in that store,and you can use and manipulate them.
Example with event Bus:
main.js:
import Vue from 'vue'
import App from './App.vue'
export const eventBus = new Vue();
new Vue({
el: '#app',
render: h => h(App)
})
User Component
<template>
<button #click="clicked">Click me to create event</button>
</template>
<script>
import { eventBus } from './main'
export default {
name: 'User',
methods: {
clicked() {
eventBus.$emit('customEvent', 'a text to pass')
}
}
}
</script>
Admin component
<template>
<p>The message from event is: {{message}}</p>
</template>
<script>
import { eventBus } from './main'
export default {
name: 'Admin',
data: () => ({
message: ''
})
created() {
eventBus.$on('customEvent', dataPassed => {
this.message = dataPassed
}
}
}
</script>
Take a look to this tutorial to learn Vuex
For your case you can use v-model like following:
<template>
<div v-if="fieldsLoaded">
<form-select v-model="fields.title" :validate="validate" ></form-select>
<form-input v-model="fields.first_name" :validate="validate" ></form-input>
</div>
</template>
v-model is essentially syntax sugar for updating data on user input events.
<input v-model="something">
is just syntactic sugar for:
<input v-bind:value="something" v-on:input="something = $event.target.value">
You can pass a prop : value in the child components, and before changing input field call a function to validate which is also passed as a prop.
Vue.component('form-select', {
props: ['options', 'value', 'onChange', 'validate'], //Added one more prop
template: '#your-template',
mounted: function () {
},
methods: {
change (opt) {
if (this.validate !== undefined) {
var isValid = this.validate(this.value)
if(!isValid) return;
}
this.$emit('input', opt)
},
},
})
I don't understand how to pass data loaded by a 'route Component' to a 'subRoute Component'..
(I'm using Vue.js with Vue-router)
My router looks like that:
router.map({
'/a': {
component: A,
subRoutes: {
'/b': {
component: B
},
'/c': {
component: C
}
}
}
});
I just want to share data loaded by component A with component B and C.
Thanks in advance !
You have two simple options.
The ugly
Use the $parent option from the subroute components. That give you access to the parent instance, it's not the recommended way, but it's effective
// from the child component
this.$parent.someData
The good
Use props. Props give you the chance to pass any data from the parent to a child. It's better, because prevents error (you pass data not an instance) and you pass only what you need, even if it isn't part of the parent data.
// parent component
<template>
<child :childsdata="parentdata"></child>
</template
<script>
export default {
data: function () {
return {
parentdata: 'Hello!'
}
}
}
</script>
// child component
<template>
{{ childsdata }} <!-- prints "Hello!" -->
</template>
<script>
export default {
props: {
childsdata: {
type: String
}
}
}
</script>