How to fire an event from child component in vuex to any parent - vuejs2

So I have Component "X" as Parent Component Or Component "Y" as
Parent Component, "a" as child component fire one or many events
Any child sibling or parent of "a" or "a" Itself can use that event
I want to use "a" as an independent component
So I Have "X" component with its own
state
mutations
actions
getters
And I have "a" component with its own
state
mutations
actions
getters
"a" a.vue file looks like this
<template>
<div>
<app-select
#change.native="someevent()"
name="lineup_id"
v-model="$store.state.form.id" label="Select Id"
:options="options"
/>
</div>
</template>
import AppSelect from "../AppSelect.vue";
export default {
data() {
return {
options:[]
};
},
components: {
AppSelect,
},
}
So I want to fire change event from child "a"
"X" and "Y" any parent get that event and do something with that event
I know how to do it with VUE

Fist : Don't use $state to get properties, use mapState
<template>
<div>
<app-select
#change.native="someevent()"
name="lineup_id"
v-model="explicitPropertyName" label="Select Id"
:options="options"
/>
</div>
</template>
<script>
import AppSelect from "../AppSelect.vue";
import { mapState } from 'vuex'
export default {
computed : {
...mapState(['explicitPropertyName'])
}
data() {
return {
options:[]
};
},
components: {
AppSelect,
},
}
</script>
Second : Use bus event, see an example
Vue.config.productionTip = false
Vue.config.devtools = false
Vue.prototype.$eventBus = new Vue()
const componentA = {
template : `<button #click="emitMethod">Component A emit</button>`,
methods: {
emitMethod () {
this.$eventBus.$emit('EVENT_NAME_FROM_A', { id: 12, pseudo: "Unicorn power A"});
}
},
mounted () {
this.$eventBus.$on('EVENT_NAME_FROM_B', function (payload) {
console.log('Emitted from component B, received in Component A', payload);
});
}
}
const componentB = {
template : `<button #click="emitMethod">Component B emit</button>`,
methods: {
emitMethod () {
this.$eventBus.$emit('EVENT_NAME_FROM_B', { id: 12, pseudo: "Unicorn power"});
}
},
mounted () {
this.$eventBus.$on('EVENT_NAME_FROM_A', function (payload) {
console.log('Emitted from component A, received in Component B', payload);
});
}
}
const vm = new Vue({
el: "#app",
components : {
componentA,
componentB
},
mounted () {
this.$eventBus.$on('EVENT_NAME_FROM_B', function (payload) {
console.log('Emitted from component B, received in Parent', payload);
});
this.$eventBus.$on('EVENT_NAME_FROM_A', function (payload) {
console.log('Emitted from component A, received in Parent', payload);
});
}
})
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="app">
<div>
<component-b></component-b>
<component-a></component-a>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
</body>
</html>

Related

Vue: emit to view component works only the first time

I have a view which has a recursive component A, which in turn embeds a component B. In component B changes are made, which should be passed via emit to component A and these should be passed directly to the view.
This "chain" works, but only the first time:
Komponent B:
// Emit in component B
function emitToParent() {
console.log("component B: ", props.current.id);
emits('changeParentReactiveData', props.current.id, data);
}
Komponent A:
// Receiving the emit in Komponent A and passing it to the view
<component-b #change-parent-reactive-data="emitToParent" />
function emitToParent(id, data) {
console.log("component A: ", id);
emits("changeParentReactiveData", id, data);
}
View:
// Receiving the emit in the View
<component-a #change-parent-reactive-data="setReactive" />
function setReactive(id, data) {
console.log("view: ", id);
}
Outcome console.log:
component B: 262194
component A: 262194
view: 262194
component B: 262187
component A: 262187
component B: 262193
component A: 262193
So as you can see, the last emit to the view component happens only the first time. Why? Does anyone have an idea?
Your code should work, As I am not able to setup the Vue 3 in the below code snippet. I am demoing it using Vue 2 setup. Please have a look and try to find out the root cause of the issue you are facing.
Every time you will click on Click Me text in the inner child component. It is emitting the events properly.
Vue.component('childComponent', {
template: `<div class="emit-time" #click="click()">Click me (Component A)</div>`,
methods: {
click() {
console.log('component A');
this.$emit('clickup', 'a click');
}
}
});
Vue.component('ParentComponent', {
data() {
return {
message: null
}
},
template: `<div><p v-text="message"></p><child-component #clickup="localMethod"></child-component></div>`,
methods: {
localMethod() {
console.log('component B');
this.message = 'Component B invoked and emitted the event to the parent';
this.$emit('clickup', 'a click');
}
}
});
new Vue({
el: "#app",
data: {
message: null
},
methods: {
localMethod() {
console.log('Root component');
this.message = 'Top most parent invoked'
}
}
});
#app {
padding : 20px;
}
div {
padding : 10px;
border : 1px dashed black;
}
div ~ div {
margin-top : 10px;
border-color : red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p v-text="message"></p>
<parent-component #clickup="localMethod"></parent-component>
</div>
Your code should work as it is. I have set up a demo here and it is working just fine. Maybe you can match your code with this-
View.vue
<script setup>
import Comp1 from './Comp1.vue'
const setReactive = (payload) => {
console.log('View', payload);
};
</script>
<template>
<v-app>
<v-main>
<Comp1 #change-parent-reactive-data="setReactive" />
</v-main>
</v-app>
</template>
Component 1
<script setup>
import Comp2 from './Comp2.vue'
const emits = defineEmits(['changeParentReactiveData'])
const emitToParent = (payload) => {
console.log('Component A', payload);
emits('changeParentReactiveData', payload);
};
</script>
<template>
<Comp2 #change-parent-reactive-data="emitToParent" :some-prop="262194"></Comp2>
</template>
Component 2
<script setup>
const props = defineProps(['someProp']);
const emits = defineEmits(['changeParentReactiveData'])
const emitToParent = () => {
console.log('Component B', props.someProp);
emits('changeParentReactiveData', props.someProp);
};
</script>
<template>
<v-btn #click="emitToParent()">
click
</v-btn>
</template>
Try implementing a global event bus to pass values from component to component.
Here's a blog to explain how:
https://blog.logrocket.com/using-event-bus-in-vue-js-to-pass-data-between-components/

Vue 3: Wait until parent is done with data fetching to fetch child data and show loader

I'm looking for a reusable way to display a full page loader (Sidebar always visible but the loader should cover the content part of the page) till all necessary api fetches has been done.
I've got a parent component LaunchDetails wrapped in a PageLoader component
LaunchDetails.vue
<template>
<PageLoader>
<router-link :to="{ name: 'launches' }"> Back to launches </router-link>
<h1>{{ name }}</h1>
<section>
<TabMenu :links="menuLinks" />
</section>
<section>
<router-view />
</section>
</PageLoader>
</template>
<script>
import TabMenu from "#/components/general/TabMenu";
export default {
data() {
return {
menuLinks: [
{ to: { name: "launchOverview" }, display_name: "Overview" },
{ to: { name: "launchRocket" }, display_name: "Rocket" },
],
};
},
components: {
TabMenu,
},
created() {
this.$store.dispatch("launches/fetchLaunch", this.$route.params.launch_id);
},
computed: {
name() {
return this.$store.getters["launches/name"];
},
},
};
</script>
PageLoader.vue
<template>
<Spinner v-if="isLoading" full size="medium" />
<slot v-else></slot>
</template>
<script>
import Spinner from "#/components/general/Spinner.vue";
export default {
components: {
Spinner,
},
computed: {
isLoading() {
return this.$store.getters["loader/isLoading"];
},
},
};
</script>
The LaunchDetails template has another router-view. In these child pages new fetch requests are made based on data from the LaunchDetails requests.
RocketDetails.vue
<template>
<PageLoader>
<h2>Launch rocket details</h2>
<RocketCard v-if="rocket" :rocket="rocket" />
</PageLoader>
</template>
<script>
import LaunchService from "#/services/LaunchService";
import RocketCard from "#/components/rocket/RocketCard.vue";
export default {
components: {
RocketCard,
},
mounted() {
this.loadRocket();
},
data() {
return {
rocket: null,
};
},
methods: {
async loadRocket() {
const rocket_id = this.$store.getters["launches/getRocketId"];
if (rocket_id) {
const response = await LaunchService.getRocket(rocket_id);
this.rocket = response.data;
}
},
},
};
</script>
What I need is a way to fetch data in the parent component (LaunchDetails). If this data is stored in the vuex store, the child component (LaunchRocket) is getting the necessary store data and executes the fetch requests. While this is done I would like to have a full page loader or a full page loader while the parent component is loading and a loader containing the nested canvas.
At this point the vuex store is keeping track of an isLoading property, handled with axios interceptors.
All code is visible in this sandbox
(Note: In this example I could get the rocket_id from the url but this will not be the case in my project so I'm really looking for a way to get this data from the vuex store)
Im introduce your savior Suspense, this feature has been added in vue v3 but still is an experimental feature. Basically how its work you create one suspense in parent component and you can show a loading when all component in any depth of your application is resolved. Note that your components should be an async component means that it should either lazily loaded or made your setup function (composition api) an async function so it will return an async component, with this way you can fetch you data in child component and in parent show a fallback if necessary.
More info: https://vuejs.org/guide/built-ins/suspense.html#suspense
You could use Events:
var Child = Vue.component('child', {
data() {
return {
isLoading: true
}
},
template: `<div>
<span v-if="isLoading">Loading …</span>
<span v-else>Child</span>
</div>`,
created() {
this.$parent.$on('loaded', this.setLoaded);
},
methods: {
setLoaded() {
this.isLoading = false
}
}
});
var Parent = Vue.component('parent', {
components: { Child },
data() {
return {
isLoading: true
}
},
template: `<div>
Parent
<Child />
</div>`,
mounted() {
let request1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
let request2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000);
});
Promise.all([ request1, request2 ]).then(() => this.$emit('loaded'))
}
});
new Vue({
components: { Parent },
el: '#app',
template: `<Parent />`
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
This may be considered an anti-pattern since it couples the parent with the child and events are considered to be sent the other way round. If you don't want to use events for that, a watched property works just fine, too. The non-parent-child event emitting was removed in Vue 3 but can be implemented using external libraries.

Access dynamic child component

How do I access my subcomponent? For example my parent component has the following 'dynamic' component (the component changes all the time at runtime).
<template>
<!-- The below component count be Component1 or Component2, etc. -->
<component id="my-cmp" v-if="templateComponent" :is="templateComponent"></component>
</template>
How can I access myCmp to call a function...this.myCmp.doSomething() doesn't work. Please note using emit here isn't a solution because emit will call doSomething() on all 'dynamic' components not just the current one.
Below is an example of my usage:
<template>
<!-- The below component count be Component1 or Component2, etc. -->
<component id="my-cmp" v-if="templateComponent" :is="templateComponent"></component>
</template>
<script type="text/javascript">
export default {
components: {
'cmp1': Component1,
'cmp2': Component1,
},
computed: {
templateComponent() {
// ...some logic that will determine if to use/chage to Component1 or Component2
return 'cmp1'
},
},
methods: {
someTrigger() {
// how can I reference #my-cmp?
// For the purpose of; myCmp.doSomething()
// I have tried the below technique BUT this will call doSomething
// on BOTH components not just the current/visible one
this.$emit('doSomethingNow') // Component1 and Component2 register using this.$parent.$on('doSomethingNow', this.doSomething)
}
}
}
</script>
use ref property,give you an example:
Vue.component('com1',{
template: '<div>component com1</div>',
methods: {
fn () {
alert('method called')
}
}
})
var app = new Vue({
el: '#app',
data () {
},
computed: {
whichCom () {
return 'com1'
}
},
methods: {
btnClick () {
this.$refs.myCom.fn()
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<component ref="myCom" v-if="whichCom" :is="whichCom"></component>
<button #click="btnClick">call method of com1</button>
</div>

how to call a method on the component by clicking Vue.js?

I am use component of the dialog window dialog.vue from vue-mdl package
<template>
<div class="mdl-dialog-container" v-show="show">
<div class="mdl-dialog">
<div class="mdl-dialog__title">{{title}}</div>
<div class="mdl-dialog__content">
<slot></slot>
</div>
<div class="mdl-dialog__actions" :class="actionsClasses">
<slot name="actions">
<mdl-button class="mdl-js-ripple-effect" #click.native.stop="close">Close</mdl-button>
</slot>
</div>
</div>
</div>
</template>
<script>
import mdlButton from './button.vue'
import createFocusTrap from 'focus-trap'
export default {
components: {
mdlButton
},
computed: {
actionsClasses () {
return {
'mdl-dialog__actions--full-width': this.fullWidth
}
}
},
data () {
return {
show: false
}
},
props: {
title: {
type: String
},
fullWidth: Boolean
},
mounted () {
this._focusTrap = createFocusTrap(this.$el)
},
methods: {
open () {
this.show = true
this.$nextTick(() => this._focusTrap.activate())
this.$emit('open')
},
close () {
this.show = false
this._focusTrap.deactivate()
this.$emit('close')
}
}
}
</script>
I want to bring a dialog window to the other component
<mdl-dialog></mdl-dialog>
<button class="mdl-button mdl-js-button mdl-button--raised">Click me</button>
I found no information on how to call a method of one component within the other. All examples are mainly used props. Tell me how to do it?
How can I call a method open() in <mdl-dialog></mdl-dialog>?
Since they're not parent child you'd want to use an event bus. Since you're using .vue files you can create a file called bus.js like
import Vue from 'vue'
export default new Vue()
Then, import that wherever you need to emit and listen for centralized events. Here's a quick example:
// SomeComponent.vue
import bus from './bus.js'
export default {
methods: {
log (msg) {
console.log(msg)
}
},
created () {
bus.$on('someEvent', this.log)
}
}
Then in another component you can do like...
// AnotherComponent.vue
import bus from './bus.js'
export default {
methods: {
emitClick (msg) {
bus.$emit('Hello from AnotherComponent.vue')
},
},
}
You can read up a bit more about it here: https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
You can create below helper method in methods in your parent component:
getChild(name) {
for(let child of this.$children) if (child.$options.name==name) return child;
},
And call child component method in this way:
this.getChild('mdl-dialog').open();
I don't test it for Vue>=2.0

I can not passing data by vue.js

I am new in vue.js..
I use webpack + vue-cli .
But I can't pass the data from parent to child
code like this..
<template lang="html">
<div>
<input v-model="parentMsg"></input>
<br>
<child v-bind:msg="parentMsg"></child>
</div>
</template>
<script>
import child from './components/lists'
export default {
data: function () {
return {
parentMsg: ''
}
},
computed: {},
ready: function () {},
attached: function () {},
methods: {},
components: {
child
}
}
</script>
<template>
<div>
{{msg}}
</div>
</template>
<script>
export default {
data: function () {
return {
msg: ''
}
}
}
</script>
By the way ...
How to bind child components to the parent array..
parent: data: array[...],
I want to bind the first of children's data to arr[0]
second to array[1]..
Is this possible?? or use v-for??
In your child component use the props property to receive parent data like:
<script>
export default {
props: ['msg'],
data: function () {
return {
}
}
}
</script>
Read more about this here