Emit an event, wait, and then emit another one in Vue - javascript

I have the following events that emit to the parent component and work fine:
this.$emit('sendToParent1', true);
this.$emit('sendToParent2');
this.$emit('sendToParent3');
this.$emit('sendToParent4', true);
this.$emit('sendToParent5', false);
However, I ran into a specific problem which requires me to do emit those 5 events, in that order, but one after another.
//emit this first and wait until the parent has finished what its supposed to do
this.$emit('sendToParent1');
//after the parent is done, we emit the next one from the child
this.$emit('sendToParent2');
//same behavior, once it is emitted to parent and the parent finishes executing, then we emit the next one...
this.$emit('sendToParent3');
//again
this.$emit('sendToParent4', true);
//and again
this.$emit('sendToParent5', false);
I believe that the best approach is to have a prop detecting when an event has finished executing whatever it is supposed to do and then sending a prop to the child, then when that prop is updated the next emit gets executed and so forth and so on. Can someone point me in the best direction of how to solve this?
The Parent has the following component:
<component v-on:sendToParent5="parentFunctionFive" v-on:sendToParent4="parentFunctionFour" v-on:sendToParent1="parentFunctionOne" v-on:sendToParent2="parentFunctionTwo" v-on:sendToParent3="parentFunctionThree"></component>
The Parent has the following methods:
parentFunctionOne: function() {
axios.get('/api/do_something-1/')
.then((res)=>{
this.something1 = res.data;
})
.catch((error)=>{
console.log(error);
});
},
parentFunctionTwo: function() {
axios.get('/api/do_something-2/')
.then((res)=>{
this.something2 = res.data;
})
.catch((error)=>{
console.log(error);
});
},
parentFunctionThree: function() {
axios.get('/api/do_something-3/')
.then((res)=>{
this.something3 = res.data;
})
.catch((error)=>{
console.log(error);
});
},
parentFunctionFour: function(passed_value) {
//changing the value of a variable in data
this.trueOrFalse = passed_value;
},
parentFunctionFive: function(passed_value) {
//changing the value of a variable in data
this.valuePassed = passed_value;
}

It would easier for us to answer if you explained what your ultimate goal is (see XY problem), but from the information that you provided I think proper way would be to pass a function called next as the event param and trigger it when the work is done which will make the process to continue to the next stage.
this.$emit('sendToParent1', {
value: true,
next:
() => this.$emit('sendToParent2', {
value: false,
next: () => this.$emit('sendToParent3', false);
})
});
parentFunctionOne: function({value, next}) {
axios.get('/api/do_something-1/')
.then((res)=>{
this.something1 = res.data;
next() // proceed
})
.catch((error)=>{
console.log(error);
});
},
parentFunctionTwo: function({value, next}) {
axios.get('/api/do_something-2/')
.then((res)=>{
this.something2 = res.data;
next() // proceed
})
.catch((error)=>{
console.log(error);
});
},
parentFunctionThree: function() {
axios.get('/api/do_something-3/')
.then((res)=>{
this.something3 = res.data;
})
.catch((error)=>{
console.log(error);
});
},

you should rethink about the way you implement the component i think you could try emit one event with all the information you need and in the parent call the appropriate function.
if you want to implement it in this way you could us a variable that you send with event and increment it every time an event has occurred and in the parent track that variable and execute the event in the right order like in the http protocole

Related

Nuxt handle redirect after deletion without errors : beforeUpdate direction not working?

So I have this nuxt page /pages/:id.
In there, I do load the page content with:
content: function(){
return this.$store.state.pages.find(p => p.id === this.$route.params.id)
},
subcontent: function() {
return this.content.subcontent;
}
But I also have an action in this page to delete it. When the user clicks this button, I need to:
call the server and update the state with the result
redirect to the index: /pages
// 1
const serverCall = async () => {
const remainingPages = await mutateApi({
name: 'deletePage',
params: {id}
});
this.$store.dispatch('applications/updateState', remainingPages)
}
// 2
const redirect = () => {
this.$router.push({
path: '/pages'
});
}
Those two actions happen concurrently and I can't orchestrate those correctly:
I get an error TypeError: Cannot read property 'subcontent' of undefined, which means that the page properties are recalculated before the redirect actually happens.
I tried:
await server call then redirect
set a beforeUpdate() in the component hooks to handle redirect if this.content is empty.
delay of 0ms the server call and redirecting first
subcontent: function() {
if (!this.content.subcontent) return redirect();
return this.content.subcontent;
}
None of those worked. In all cases the current page components are recalculated first.
What worked is:
redirect();
setTimeout(() => {
serverCall();
}, 1000);
But it is obviously ugly.
Can anyone help on this?
As you hinted, using a timeout is not a good practice since you don't know how long it will take for the page to be destroyed, and thus you don't know which event will be executed first by the javascript event loop.
A good practice would be to dynamically register a 'destroyed' hook to your page, like so:
methods: {
deletePage() {
this.$once('hook:destroyed', serverCall)
redirect()
},
},
Note: you can also use the 'beforeDestroy' hook and it should work equally fine.
This is the sequence of events occurring:
serverCall() dispatches an update, modifying $store.state.pages.
content (which depends on $store.state.pages) recomputes, but $route.params.id is equal to the ID of the page just deleted, so Array.prototype.find() returns undefined.
subcontent (which depends on content) recomputes, and dereferences the undefined.
One solution is to check for the undefined before dereferencing:
export default {
computed: {
content() {...},
subcontent() {
return this.content?.subcontent
👆
// OR
return this.content && this.content.subcontent
}
}
}
demo

Subscribing to a change, but needing to see variable from where Service is used?

My code has been refactored and some extracted into a service that subscribes to functions. However, my original code had a call within the subscription that referenced a variable within the file, but now I'm not sure how to best reach it?
I am struggling with where to place the line:
this.select.reset('some string'); found within the subscribeToMessageService() function.
Original code
event.component.ts
select: FormControl;
#ViewChild('mySelect') mySelect: ElementRef;
subscribeToMessageService() {
this.messageService.serviceMsg
.subscribe(res => {
// unrelated code
this.select.reset('some string');
});
}
subscribeToEventService() {
this.eventService.eventSubject
.subscribe(res => {
this.select = new FormControl(res.status);
this.select.valueChanges.subscribe(value => {
// manual blurring required to stop error being thrown when popup appears
this.selector.nativeElement.blur();
// do something else
});
});
}
Refactored code
status.service.ts
subscribeToMessageService(): void {
this.messageService.serviceMsg
.subscribe(res => {
// unrelated code
// This is where 'this.select.reset('some string');' would have gone
});
}
status.component.ts
select: FormControl;
#ViewChild('exceptionalSelect') selector: ElementRef;
subscribeToEventService() {
this.eventService.eventSubject
.subscribe(res => {
this.select = new FormControl(res.status);
this.select.valueChanges.subscribe(value => {
// manual blurring required to stop error being thrown when popup appears
this.selector.nativeElement.blur();
this.onStatusChange(value);
});
});
}
Since you still want to subscribe to the original source messageService.serviceMsg your new StatusService needs to expose this observable to the injecting component (StatusComponent).
This can be done for example by creating a public observable in the StatusService (possibly by utilising rxjs Subject or angular EventEmitter) and triggering the emit in the subscription of messageService.serviceMsg.
Then your StatusComponent only needs to inject StatusService and do
this.statusService.serviceMsg // <-- might choose another name to make clear that this is passed on.
.subscribe(res => {
// unrelated code
this.select.reset('some string');
});

Angular 5, execute new function if the first function works

I would like to launch a new request if this function in relation with my services works, how to proceed? Thank you
test.component.ts
destroyUnicorn(item){
this.itemService.updateUnicorn( {
statut: "destroyed",
id: item.id
});
}
item.service.ts
updateUnicorn(item) {
this.itemDoc = this.afs.doc("unicorns/${item.id}");
this.itemDoc.update(item) <------ FireStore request
.then(function() {})
.catch(function() {});
}
Global idea :
-- 1 ° In my template, I click on a button who execute the function deleteUnicorn of my component.
--- 2 ° The deleteUnicorn function sends the parameters to the updateUnicorn function in my services, which sends a request to Firestore to modify the content in the database.
-- 3 ° I would like, when the function is finished and works, to be able to execute a new function which will modify the user's money in another table of the database.
You can chain promises. Change updateUnicorn() method in order to return a resolved promise and then add your desired functionality:
destroyUnicorn(item){
this.itemService.updateUnicorn( {
statut: "destroyed",
id: item.id
})
.then(function(something) {
// This will execute if updateUnicorn resolves.
});
}
And in your updateUnicorn method:
updateUnicorn(item) {
this.itemDoc = this.afs.doc("unicorns/${item.id}");
this.itemDoc.update(item) <------ FireStore request
.then(function(something) {
return Promise.resolve(something);
})
.catch(function() {});
}
Also, if you don't need to use the response of the itemDoc.update() method, you could simply update the function like this:
updateUnicorn(item) {
this.itemDoc = this.afs.doc("unicorns/${item.id}");
return this.itemDoc.update(item);
}
And the destroyUnicorn() will remain the same.

Firebase: snap.child(...).once is not a function

I want to listen to some data from my firebase but I first have to check if the keys leading to the specific data has already been made. If they have not yet been made there is no problem I don't have to listen.
I get this error message:
snip.child(...).once is not a function
I have tried to just followed their documentation from here it really seems as if what I am doing should be legal.
The .hasChild(...) works fine.
rootRef.child('match-feed-comments').once('value', snip => {
if (snip.hasChild(this.props.matchId)) { // Checking if child exists
snip.child(this.props.matchId).once('value', snup => { // if the child exists we listen
if (snup.hasChild(this.props.feedComponentId)) {
snup.child(this.props.feedComponentId).on('value', snap => {
// finally I want to do stuff with the data
this.setState({
comments: snap.val()
})
});
}
});
}
});

How to listen on a single child change in react native firebase?

I have below code use once('value') to retrieve user's contact list by index. Then if someone change description, my contact list will not update in real time because it is once(). I can't use on() because this will cause crazy query leak. So where should I place on('child_added') to listen on every single node.
getContacts(userID) {
var contactList = [];
this.ref.child('userContacts').child(userID).once('value', (contacts) => {
contacts.forEach((contact) => {
this.ref.child('users').child(contact.key()).once('value', (contact) => {
contactList.push({
key: contact.key(),
name: contact.val().name,
description: contact.val().description,
numberOfProduct: contact.val().numberOfProduct
});
this.setState({
dataSource: this.state.dataSource.cloneWithRows(contactList)
});
})
})
})
}
If you want to receive updates for an object, you will have to attach a listener with on().
If you want to prevent attaching multiple listeners to the same user, keep an object where you keep the listener for each user-key you attached. Then before attaching a new user-listener, check if it already exists:
var userListenersByKey = {};
this.ref.child('userContacts').child(userID).once('value', (contacts) => {
contacts.forEach((contact) => {
if (!userListenersByKey[contact.key()]) {
userListenersByKey[contact.key()] = this.ref.child('users').child(contact.key()).on('value', (contact) => {
contactList.push({
key: contact.key(),
name: contact.val().name,
description: contact.val().description,
numberOfProduct: contact.val().numberOfProduct
});
this.setState({
dataSource: this.state.dataSource.cloneWithRows(contactList)
});
});
}
});
})
You'll also want to remove these listeners at some point, so it might be better to listen for child_added and child_removed for userContacts and use that to (optionally) add and remove the user-listeners.

Categories