Vue SFC - Wait for child's event to be emitted then the function end - vuejs2

I have an async method in the parent component which opens a b-modal component inside the child component, I would like to wait until the hide event is emitted and the associated function ends in order to continue the execution of the function.
Parent :
<template>
<div>
<b-button #click="modal_queue()">
Open modal
</b-modal>
<child ref="myChild"></child>
</div>
</template>
export default {
methods: {
async modal_queue() {
for(let i=0,i<2<i++) {
await this.aFunction();
}
},
async aFunction() {
return new Promise(resolve => {
this.$refs.mychild.$refs.myModal.$on("hide",() => {
resolve();
}
})
}
}
}
Child :
<template>
<b-modal ref="myModal" #hide="hidden()">
</b-modal>
</template>
export default {
methods: {
hidden() {
// Something
}
}
}
So I would like to wait until the Something code ends in order to resolve my Promise.
How I could do that ? Is my code architecture bad ?
For the moment, I'm only waiting until the 'hide' event is emitted.

Related

Modify child component prop/data in parent's event handler

I am using vue.js 2 and ant design. I encountered such an issue when trying to change the loading state of a button in a modal, which is in an embedded component. Here is a similar example:
Code of modal component:
Modal.vue
<template>
<a-button :loading="loading" #click="onSubmit">Submit</a-button>
</template>
<script>
export default {
data(){
return {
loading: false
}
},
methods: {
onSubmit(){
this.$emit('submit')
}
}
}
</script>
Index.vue
<modal
#submit="submit" />
<script>
export default {
methods: {
submit(){
await axios.create(blabla...) //some async api call
}
}
}
</script>
Two failed approach:
Set the value of loading before and after $emit in onSubmit. It won't wait for the async request to finish.
Make loading a prop of Modal component and change it in the event handler. Nothing happens.
The only workable I have found so far is to create a method like setLoading in child component, pass that to the event handler as a parameter and call it. I was wondering if there is a more succinct way and what is the mechanism behind these.
you should pass the loading state as props. and add async before submitting the method. don't know why you mention failed cases. hard to tell without looking in that code. but try again with this code. maybe there are errors. so wrap in a try-catch block when you fetch details.
<modal
#submit="submit" :loading="loading" />
<script>
export default {
data(){
return {
loading: false,
}
}
methods: {
async submit(){ // add async keyword
this.loading = true
try{ // use try-catch block. see errors if there are any.
await axios.create(blabla...) //some async api call
}
catch(error){
console.log(error.response)
}
this.loading = false
}
}
}
</script>
Modal.vue
<template>
<a-button :loading="loading" #click="onSubmit">Submit</a-button>
</template>
<script>
props: {
loading: Boolean
}
</script>

Vuejs $emit doesnt work in some part of a function, and in other part works

I am using vuejs3 and trying to emit event from a child component.
child Component
<input type="button" v-if="edition_mode" #click="cancel()" class="btn btn-primary" value="Annuler">
[...]
cancel(){
if(this.new_sav){
this.$emit('test')
}else{
console.log('else')
this.$emit('test')
}
},
Parent Component
<div v-if="creation_form">
<h4>Ajout Nouveau Sav</h4>
<sav-form
:initial_data="null"
:li_product="li_product"
:new_sav="true"
:customer_id="data.customer.id"
#action="form_action"
#test="test()"/>
</div>
[...]
test(){
console.log('test emit works')
}
When cancel() is executed, in the if case $emit() works correctly, but in the else case, only 'else' is printed and $emit is not executed. What I am doing wrong here ?
I also have several buttons in child component, in the same div, that all call differents function but some function 'can' emit event and other can't.
I am not sure what issue you are facing but it is working fine in the below code snippet. Please have a look and let me know if any further clarification/discussion required.
Demo :
Vue.component('child', {
template: '<div><button #click="cancel()">Trigger emit event from child!</button></div>',
data() {
return {
isValid: true
}
},
methods: {
cancel: function() {
this.isValid = !this.isValid;
if (this.isValid) {
console.log('valid');
this.$emit('test');
} else {
console.log('not valid');
this.$emit('test')
}
}
}
});
var vm = new Vue({
el: '#app',
methods: {
getTestEvent() {
console.log('Emit event from child triggered!');
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<child #test="getTestEvent"></child>
</div>
I was facing the same issue.
I use Vuex to store authenticated status of the user and my mistake was to use v-if="isAuth" attribute in parent component and the child component set isAuth to false through the store so this.$emit() doesn't work anymore.
Parent Component:
<login-form #onLogin="onLoginHandler" v-if="!isAuth" />
Child Component:
methods: {
example() {
[...]
const response = await axios.post("/login", data);
if (response.status === 200) {
// under the hood isAuth is set to True;
this.$store.dispatch("SET_AUTH", true);
}
// Doesn't work anymore because the parent component v-if is now False.
this.$emit("onLogin", response.data);
}
}

Vue.js Dynamically extend/replace child component method at runtime with access to parent scope

Is it possible to extend child component function at runtime in vue? I want to limit/stop child component function call based on parent scope logic (I want to avoid passing props in this specific case).
Overriding a component method is not a runtime solution/I can't have access to parent scope.
What I have tried and it does not working:
// Foo.vue
<template>
<button #click="func">Click me</button>
</template>
export default {
methods: {
func() {
console.log('some xhr')
}
}
}
// Bar.vue
<template>
<Foo ref="foo"/>
</template>
export default {
components: {Foo}
mounted() {
this.$nextTick(() => {
this.$refs.foo.func = function() {
console.log('some conditional logic')
this.$refs.foo.func()
}
})
}
}
For this usecase a better implementation would be defining the function in the parent itself and passing it through props. Since props are by default reactive you can easily control it from parent.
// Foo.vue
<template>
<button #click="clickFunction.handler">Click me</button>
</template>
export default {
name: 'Foo',
props: {
clickFunction: {
type: Object,
required: true
}
}
}
// Bar.vue
<template>
<Foo :clickFunction="propObject"/>
</template>
export default {
components: {Foo},
data() {
return {
propObject: {
handler: null;
}
};
}
mounted() {
this.$nextTick(() => {
if(some condition) {
this.propObject.handler = this.func();
} else this.propObject.handler = null;
})
},
methods: {
func() {
console.log('some xhr')
}
}
}
From what I managed to realize:
the solution in the code posted in the question really replaces the func() method in the child component. It's just that Vue has already attached the old method to the html element. Replacing it at the source will have no impact.
I was looking for a way to re-attach the eventListeners to html component. Re-rendering using an index key would not help because it will re-render the component with its original definition. You can hide the item in question for a split second, and when it appears you will receive an updated eventListener. However, this involves an intervention in the logic of the child component (which I avoid).
The solution is the $forceUpdate() method.
Thus, my code becomes the following:
// Foo.vue
<template>
<button #click="func">Click me</button>
</template>
export default {
methods: {
func() {
console.log('some xhr')
}
}
}
// Bar.vue
<template>
<Foo ref="foo"/>
</template>
export default {
components: {Foo}
mounted() {
this.$nextTick(() => {
let original = this.$refs.foo.func; // preserve original function
this.$refs.foo.func = function() {
console.log('some conditional logic')
original()
}
this.$refs.btn.$forceUpdate(); // will re-evaluate visual logic of child component
})
}
}

Vue directive not triggering method

I have a custom vue directive.
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
document.addEventListener(clickHandler, (event) => {
const clickedInsideDropdown = el.contains(event.target);
if (!clickedInsideDropdown && el.classList.contains(openClass)) {
vnode.context.$emit(binding.expression);
}
});
}
});
I then initialize it with the dropdown template:
<template>
<div class="dropdown" :class="{ '-is-open': open }" v-click-outside="close">
<span #click="toggle">
<slot name="toggle"></slot>
</span>
<slot name="menu"></slot>
</div>
</template>
The supporting logic is functioning as expected as well:
<script>
export default {
data: function () {
return {
open: false
}
},
methods: {
close: function () {
this.open = false;
console.log('close');
},
toggle: function () {
this.open = !this.open;
console.log('toggle');
}
}
}
</script>
The Problem
The event should fire when the current dropdown _is open and none of the items inside of it are clicked - which is does (console logging confirms this). However, the $emit is not triggering the close method for some reason.
The event is being emitted in the Vue devtools as expected.
Vue version 2.5.3
Credits to Linus Borg who answered my question for me on the forum. Was just understanding the purpose of events incorrectly.
Events are usually used to communicate from a child component to a parent component, so triggering an event ‘close’ in a componet will not run a method of that name in that component.
If you want that, you have to actually register a listener to that event:
created () {
this.$on('close', this.close /*the name of the method to call */)
}
However, this isn’t really necessary in your case. you are already passing the close method to the directive, so you can run it directly:
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
document.addEventListener(clickHandler, (event) => {
const clickedInsideDropdown = el.contains(event.target);
if (!clickedInsideDropdown && el.classList.contains(openClass)) {
binding.value()
// alternartively, you could also call the method directly on the instance, no need for an event:
vnode.context.[expression]()
// but that wouldn't really be elegant, agreed?
}
});
}
});

How can I call method in other component on vue.js 2?

My first component like this :
<template>
...
</template>
<script>
export default {
...
methods: {
addPhoto() {
const data = { id_product: this.idProduct}
const item = this.idImage
this.$store.dispatch('addImage', data)
.then((response) => {
this.createImage(item, response)
});
},
}
}
</script>
If the method addPhoto called, it will call ajax and then it will get response ajax
I want to send response ajax and another parameter to method createImage. Method createImage is located in other component (second component)
My second component like this :
<template>
<div>
<ul class="list-inline list-photo">
<li v-for="item in items">
<div v-if="clicked[item]">
<img :src="image[item]" alt="">
<span class="fa fa-check-circle"></span>
</div>
<a v-else href="javascript:;" class="thumb thumb-upload"
title="Add Photo">
<span class="fa fa-plus fa-2x"></span>
</a>
</li>
</ul>
</div>
</template>
<script>
export default {
...
data() {
return {
items: [1,2,3,4,5],
clicked: [], // using an array because your items are numeric
}
},
methods: {
createImage(item, response) {
this.$set(this.clicked, item, true)
},
}
}
</script>
How can I run the createImage method on the second component and after that it can change the element in the second component?
No two components don't have a parent/child relation. They are all connected through the root vue instance. To access the root vue instance just call this.$root and you get the root instance.
....
.then((response) => {
this.$root.$emit('createImage', item, response)
});
and in the second component make the function that needs to be triggered
...
mounted() {
this.$root.$on('createImage', (item, response) => {
// your code goes here
})
}
It acts more like a socket. The event will be globally available, due to $root.
N.B. adding the vue instance to global window object is a bad practice
If these 2 components are siblings (no parent & child), then one solution is to use event bus.
General idea is to build a global event handler like so:
in main.js
window.Event = new Vue();
Then in your first component fire an event:
....
.then((response) => {
Event.$emit('createImage', item, response)
});
and in second component register a handler for listening to createImage event in mounted() hook:
...
mounted() {
Event.$on('createImage', (item, response) => {
// your code goes here
}
}
You can find more info by reading this turtorial and watching this screen cast.