Dynamic component event bus not work in vue? - vue.js

I just used event bus in vuejs for the dynamic component, but when I emit an event from one component, I cannot listen to that event in another component. Below is the demo I created by codesandbox, what I want to do is output 'test in tes2' from test2component, but it outputs nothing. But when I uncomment the $bus listener in HomeCompnent, the two console.log are all executed.
main.js
Vue.propotype.$bus = new Vue();
HomeComponent
<template>
<div>
<component :is="currentComponent" #changeComponent="changeComponent"></component>
</div>
</template>
<script>
import Test1Component from "./Test1Component";
import Test2Component from "./Test2Component";
export default {
created() {
// this.$bus.$on("test", () => console.log("event bus test"));
},
components: {
Test1Component,
Test2Component
},
data() {
return {
currentComponent: "Test1Component"
};
},
methods: {
changeComponent() {
this.currentComponent = "Test2Component";
}
}
};
</script>
<style>
</style>
Test1Component
<template>
<div>
<h1>test1</h1>
<button #click="changeComponent">click me</button>
</div>
</template>
<script>
export default {
methods: {
changeComponent() {
this.$emit("changeComponent");
this.$bus.$emit("test");
}
}
};
</script>
<style>
</style>
Test2Component
<template>
<h1>test2</h1>
</template>
<script>
export default {
created() {
this.$bus.$on("test", () => console.log("test in test2"));
}
};
</script>
<style>
</style>

This problem is a good demonstration of the downsides of the event bus pattern:
You, the developer, have to carefully ensure that sender and receiver of all events actually exist at the same moment in time.
In your scenario, that's not the case:
When Test1Component emits the event, Test2Component doesn't exist yet.
After Test2Component has been created by HomeComponent just a moment later, the event is already "gone".
My usual disclaimer as a Vue core team member: Don't use the Event bus pattern.

Event Bus is just fine but problem is in implementation.
You are very correct in case of you un-comment code In home component, I tested on your snippet it emit only once event bus test [console.log]
With events you need to take care this
You need to define listener. [ make sure you define them before emitting event ]
Now you just emit event. [ to work we need listener to listen first]
In your case you wrote listener function $on in created event of theTest2Component
Now just think for now, You don't have any listeners at beginning as in home compo you just commented that listener code .[ its initial step ]
Now when you click on click me button you are changing component to new component[Test2Component], It is mounted and its created event will fire then this listener will start listening to event
but you missed this
this.$emit("changeComponent"); // this is first [ fires/emit ]
this.$bus.$emit("test"); // THEN THIS EMIT
So, When it just start changing component from compo1 tocompo2testfired/emitted directly without wait, its notsynchronousit isAsynchronousIt will not wait to finish all stuff ofchangeComponent. It will be emitted immediately [test`]
Guess what Now when test emits, at that time Dom operation to add component, ITS NOT DONE YET, and test is emitted
So, No listeners are there, so no console.log
But if you see if, you UNCOMMENT listener in home function listener is well defined before emit of test event so it output in console.
I hope you understand it, if not let me know point I will explain it in details
Another thins is that you added $bus in prototype so all components has its own bus. instead you can use GLOBAL event Bus
as in example you can see.
in es6 you can do
//bus.js
const bus = new Vue({});
export default bus;
to import it in other components
import bus from './bus.js';
// ... do bus.$on ..
// ... do bus.$emit ..
var $bus = new Vue({});
Vue.component('Test1', {
template: `
<div class="blog-post">
<h3>test1</h3>
<button #click="changeComponent">click me</button>
</div>
`,
methods: {
changeComponent() {
$bus.$emit("chng");
$bus.$emit("test");
}
}
});
Vue.component('Test2', {
template: `
<div class="blog-post">
<h3>test2</h3>
</div>
`,
created() {
$bus.$on("test", () => console.log("test in test2 will not fire as we are little late to listen it"));
}
});
new Vue({
el: '#app',
created: function(){
console.log('created');
$bus.$on("chng", () => this.changeComponent());
$bus.$on("test", () => console.log("test in test2 main/HOME"));
},
data() {
return {
currentComponent: "Test1"
};
},
methods: {
changeComponent() {
this.currentComponent = "Test2";
}
}
});
<!DOCTYPE html>
<html>
<head>
<script> console.info = function(){} </script>
<script src="https://code.jquery.com/jquery.min.js"></script>
<script src="https://vuejs.org/js/vue.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Stack Overflow - Hardik Satasiya</title>
<style>.half{width:50%;float:left;}</style>
</head>
<body>
<div id="app">
<component :is="currentComponent" #changeComponent="changeComponent"></component>
</div>
</body>
</html>

Your test is not correct.
Basically, Test2Component is not created in the time Test1Component emit this.$bus.$emit("test");
You saw 2 console.log because codesandbox use hot reload modules, so this.$bus.$on("test", () => console.log("test in test2")); is still registered if when you change your code.
If you unregister when component destroyed in Test2Component, test in test2 will never be logged
Test2Component.vue
<template>
<div>
<h1>test2</h1>
<button #click="changeComponent">click me</button>
</div>
</template>
<script>
export default {
created() {
console.log("Component 2 is created");
this.$bus.$on("test", this.testLog);
},
beforeDestroy() {
this.$bus.$off("test", this.testLog);
},
methods: {
testLog() {
console.log("test in test2");
},
changeComponent() {
this.$emit("changeComponent");
}
}
};
</script>
<style>
</style>
Demo: https://codesandbox.io/s/2485jw460y
If you comment
beforeDestroy() {
this.$bus.$off("test", this.testLog);
},
in component 2, you will see test in test2 is printed many times after few clicks
(if you change the code, please refresh to run from beginning)

Related

How to test "errorComponent" in "defineAsyncComponent" in Vue?

I was learning about Async Components in Vue. Unfortunately in that documentation Vue did not show any example of using Async Components in the <template> part of a Vue SFC. So after searching on the web and reading some articles like this one and also this one, I tried to use this code to my Vue component:
<!-- AsyncCompo.vue -->
<template>
<h1>this is async component</h1>
<button #click="show = true">login show</button>
<div v-if="show">
<LoginPopup></LoginPopup>
</div>
</template>
<script>
import { defineAsyncComponent, ref } from 'vue';
import ErrorCompo from "#/components/ErrorCompo.vue";
const LoginPopup = defineAsyncComponent({
loader: () => import('#/components/LoginPopup.vue'),
/* -------------------------- */
/* the part for error handling */
/* -------------------------- */
errorComponent: ErrorCompo,
timeout: 10
}
)
export default {
components: {
LoginPopup,
},
setup() {
const show = ref(false);
return {
show,
}
}, // end of setup
}
</script>
And here is the code of my Error component:
<!-- ErrorCompo.vue -->
<template>
<h5>error component</h5>
</template>
Also here is the code of my Route that uses this component:
<!-- test.vue -->
<template>
<h1>this is test view</h1>
<AsyncCompo></AsyncCompo>
</template>
<script>
import AsyncCompo from '../components/AsyncCompo.vue'
export default {
components: {
AsyncCompo
}
}
</script>
And finally the code of my actual Async component called LoginPopup.vue that must be rendered after clicking the button:
<!-- LoginPopup.vue -->
<template>
<div v-if="show1">
<h2>this is LoginPopup component</h2>
<p>{{retArticle}}</p>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
export default {
setup() {
const getArticleInfo = async () => {
// wait 3 seconds to mimic API call
await new Promise(resolve => setTimeout(resolve, 3000));
const article = "my article"
return article
}
const show1 = ref(false);
const retArticle = ref(null);
onMounted(
async () => {
retArticle.value = await getArticleInfo();
show1.value = true;
}
);
return {
retArticle,
show1
}
}
}
</script>
When I comment the part below from AsyncCompo.vue everything works correctly and my component loads after 3s when I clicks the button:
errorComponent: ErrorCompo,
timeout: 10
But I want to test the error situation that Vue says in my component. I am not sure that my code implementation is absolutely true, but with code above when I use the errorComponent, I receive this warning and error in my console:
I also know that we could handle these situations with <Suspense> component, but because my goal is learning Async Components, I don't want to use them here. Could anyone please help me that how I can see and test my "error component" in the page? is my code wrong or I must do something intentionally to make an error? I don't know but some articles said that with decreasing timeout option I could see error component, but for me it gives that error.

Async loading child component doesn't trigger v-if

Hi everyone and sorry for the title, I'm not really sure of how to describe my problem. If you have a better title feel free to edit !
A little bit of context
I'm working on a little personal project to help me learn headless & micro-services. So I have an API made with Node.js & Express that works pretty well. I then have my front project which is a simple one-page vue app that use vuex store.
On my single page I have several components and I want to add on each of them a possibility that when you're logged in as an Administrator you can click on every component to edit them.
I made it works well on static elements :
For example, here the plus button is shown as expected.
However, just bellow this one I have some components, that are loaded once the data are received. And in those components, I also have those buttons, but they're not shown. However, there's no data in this one except the title but that part is working very well, already tested and in production. It's just the "admin buttons" part that is not working as I expect it to be :
Sometimes when I edit some codes and the webpack watcher deal with my changes I have the result that appears :
And that's what I expect once the data are loaded.
There is something that I don't understand here and so I can't deal with the problem. Maybe a watch is missing or something ?
So and the code ?
First of all, we have a mixin for "Auth" that isn't implemented yet so for now it's just this :
Auth.js
export default {
computed: {
IsAdmin() {
return true;
}
},
}
Then we have a first component :
LCSkills.js
<template>
<div class="skills-container">
<h2 v-if="skills">{{ $t('skills') }}</h2>
<LCAdmin v-if="IsAdmin" :addModal="$refs.addModal" />
<LCModal ref="addModal"></LCModal>
<div class="skills" v-if="skills">
<LCSkillCategory
v-for="category in skills"
:key="category"
:category="category"
/>
</div>
</div>
</template>
<script>
import LCSkillCategory from './LCSkillCategory.vue';
import { mapState } from 'vuex';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
import Auth from '../../mixins/Auth';
export default {
name: 'LCSkills',
components: {
LCSkillCategory,
LCAdmin,
LCModal,
},
computed: mapState({
skills: (state) => state.career.skills,
}),
mixins: [Auth],
};
</script>
<style scoped>
...
</style>
This component load each skills category with the LCSkillCategory component when the data is present in the store.
LCSkillCategory.js
<template>
<div class="skillsCategory">
<h2 v-if="category">{{ name }}</h2>
<LCAdmin
v-if="IsAdmin && category"
:editModal="$refs.editModal"
:deleteModal="$refs.deleteModal"
/>
<LCModal ref="editModal"></LCModal>
<LCModal ref="deleteModal"></LCModal>
<div v-if="category">
<LCSkill
v-for="skill in category.skills"
:key="skill"
:skill="skill"
/>
</div>
<LCAdmin v-if="IsAdmin" :addModal="$refs.addSkillModal" />
<LCModal ref="addSkillModal"></LCModal>
</div>
</template>
<script>
import LCSkill from './LCSkill.vue';
import { mapState } from 'vuex';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
import Auth from '../../mixins/Auth';
export default {
name: 'LCSkillCategory',
components: { LCSkill, LCAdmin, LCModal },
props: ['category'],
mixins: [Auth],
computed: mapState({
name: function() {
return this.$store.getters['locale/getLocalizedValue']({
src: this.category,
attribute: 'name',
});
},
}),
};
</script>
<style scoped>
...
</style>
And so each category load a LCSkill component for each skill of this category.
<template>
<div class="skill-item">
<img :src="img(skill.icon.hash, 30, 30)" />
<p>{{ name }}</p>
<LCAdmin
v-if="IsAdmin"
:editModal="$refs.editModal"
:deleteModal="$refs.deleteModal"
/>
<LCModal ref="editModal"></LCModal>
<LCModal ref="deleteModal"></LCModal>
</div>
</template>
<script>
import LCImageRendering from '../../mixins/LCImageRendering';
import { mapState } from 'vuex';
import Auth from '../../mixins/Auth';
import LCAdmin from '../LCAdmin.vue';
import LCModal from '../LCModal.vue';
export default {
name: 'LCSkill',
mixins: [LCImageRendering, Auth],
props: ['skill'],
components: { LCAdmin, LCModal },
computed: mapState({
name: function() {
return this.$store.getters['locale/getLocalizedValue']({
src: this.skill,
attribute: 'name',
});
},
}),
};
</script>
<style scoped>
...
</style>
Then, the component with the button that is added everywhere :
LCAdmin.js
<template>
<div class="lc-admin">
<button v-if="addModal" #click="addModal.openModal()">
<i class="fas fa-plus"></i>
</button>
<button v-if="editModal" #click="editModal.openModal()">
<i class="fas fa-edit"></i>
</button>
<button v-if="deleteModal" #click="deleteModal.openModal()">
<i class="fas fa-trash"></i>
</button>
</div>
</template>
<script>
export default {
name: 'LCAdmin',
props: ['addModal', 'editModal', 'deleteModal'],
};
</script>
Again and I'm sorry it's not that I haven't look for a solution by myself, it's just that I don't know what to lookup for... And I'm also sorry for the very long post...
By the way, if you have some advice about how it is done and how I can improve it, feel free, Really. That how I can learn to do better !
EDIT :: ADDED The Store Code
Store Career Module
import { getCareer, getSkills } from '../../services/CareerService';
const state = () => {
// eslint-disable-next-line no-unused-labels
careerPath: [];
// eslint-disable-next-line no-unused-labels
skills: [];
};
const actions = {
async getCareerPath ({commit}) {
getCareer().then(response => {
commit('setCareerPath', response);
}).catch(err => console.log(err));
},
async getSkills ({commit}) {
getSkills().then(response => {
commit('setSkills', response);
}).catch(err => console.log(err));
}
};
const mutations = {
async setCareerPath(state, careerPath) {
state.careerPath = careerPath;
},
async setSkills(state, skills) {
state.skills = skills;
}
}
export default {
namespaced: true,
state,
actions,
mutations
}
Career Service
export async function getCareer() {
const response = await fetch('/api/career');
return await response.json();
}
export async function getSkills() {
const response = await fetch('/api/career/skill');
return await response.json();
}
Then App.vue, created() :
created() {
this.$store.dispatch('config/getConfigurations');
this.$store.dispatch('certs/getCerts');
this.$store.dispatch('career/getSkills');
this.$store.dispatch('projects/getProjects');
},
Clues
It seems that if I remove the v-if on the buttons of the LCAdmin, the button are shown as expected except that they all show even when I don't want them to. (If no modal are associated)
Which give me this result :
Problem is that refs are not reactive
$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.
See simple demo below...
const vm = new Vue({
el: "#app",
components: {
MyComponent: {
props: ['modalRef'],
template: `
<div>
Hi!
<button v-if="modalRef">Click!</button>
</div>`
}
},
data() {
return {
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-component :modal-ref="$refs.modal"></my-component>
<div ref="modal">I'm modal placeholder</div>
</div>
The solution is to not pass $ref as prop at all. Pass simple true/false (which button to display). And on click event, $emit the event to the parent and pass the name of the ref as string...

How to force DOM element in VueJS to be rendered within the App.vue component

I am currently playing with Clippy.js, a js implementation of the old school Microsoft Agent.
The problem occurs as I want to render the agent inside the App.vue component in my VueJS project. Though, i can't seem to figure out how to bind the agent to App.vue component, or any of its children.
The agent is loaded as follows:
<script>
import 'clippyjs';
methods: {
clippy() {
clippy.load('Clippy', (agent) => {
agent.show();
agent.animate();
});
}
}
</script>
The code is from a method in my Header.app component, which is a child component of App.vue
LINK TO DIRECTORY : https://github.com/pi0/clippyjs
Below is one simple demo to integrate clippy.
The key point is fetch the agent instance inside the callback of clippy.load, then execute the actions you need by invoke the related APIs agent instance provides.
Clippy.js will add extra Dom elements under <body>, so we'd better to clear them up if necessary (like: component destroyed).
Anyway, the implementation of Clippy.js is based on real Dom through JQuery, Vue is based on Virtual Node. It makes the things a little complicate if uses Clippy.js in Vue project.
Vue.component('v-clippy',{
template:`
<div>
<button v-show="!this.agent" #click="loadClippy()">Load Clippy</button>
<button v-show="this.agent" #click="show()">Show</button>
<button v-show="this.agent" #click="hide()">Hide</button>
<button v-show="this.agent" #click="play()">Play</button>
<button v-show="this.agent" #click="animate()">Animate</button>
<button v-show="this.agent" #click="destroy()">Destroy</button>
</div>
`,
props: {
name: {
type: String,
default: 'Links'
}
},
data () {
return {
agent: null
}
},
mounted: function () {
this.loadClippy()
},
methods: {
loadClippy: function () {
clippy.load(this.name, (agent) => {
this.agent = agent
})
},
show: function () {
this.agent && this.agent.show()
},
hide: function () {
this.agent && this.agent.hide()
},
play: function () {
this.agent && this.agent.play('Searching')
},
animate: function () {
this.agent.animate()
},
destroy: function () {
this.agent._el.remove() // this.agent._el is the JQuery object binds to the Dom of Agent
this.agent = null
}
}
})
new Vue ({
el:'#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<link rel="stylesheet" type="text/css" href="https://gitcdn.xyz/repo/pi0/clippyjs/master/assets/clippy.css">
<script src="https://unpkg.com/jquery#3.2.1"></script>
<script src="https://unpkg.com/clippyjs#latest"></script>
<div id="app">
<div>
<h3>Test Clippy</h3>
<div>
<v-clippy></v-clippy>
<v-clippy name="Merlin"></v-clippy>
</div>
</div>
</div>

vm.$on listener isn't firing from the parent mounted() method

It seems very simple but not working for me. I am trying to fire event from a child component and listen to it from the parent component using the mounted() method by using the vm.$on() instance method but it's not working.
For example, I have created a very basic Vue CLI App on CodeSandbox to reproduce the issue. Any help would be much appreciated.
Parent component: App.vue
<template>
<div id="app">
<HelloWorld />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
HelloWorld
},
mounted() {
this.$on('icecream', () => console.log('not good for children'));
},
};
</script>
Child component: HelloWorld.vue
<template>
<div class="hello">
<h3>Emit event from child and listen from parent</h3>
<button #click="emitAnEvent()">Emit</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
methods: {
emitAnEvent() {
this.$emit('icecream');
}
},
};
</script>
There's also another option to handle the $emit() written in the Vue Documentation here.
<HelloWorld v-on:icecream="CatchIceCream()" />
.
.
.
methods: {
CatchIceCream() {
console.log('not good for children');
}
},
Never mind! I was able to figure it out based on the answer given here. I had to $emit() event directly to the $parent like the following:
this.$parent.$emit('icecream');
While this could be a bit problematic in different scenarios, it's completely fine on my case.

Vuejs vue-nav-tabs change title of tabs [duplicate]

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();