Vue return undefined during emit event - vue.js

I read a code, where it allows user to download attachment
<template #link="{ item, rowdata }">
<attachment-link
v-test-id="'attachment-link'"
:inventory-id="item"
:filename="rowdata['fileName']"
#download-attachment="downloadAttachment">
</attachment-link>
</template>
But in the downloadAttachment function, it returns undefined, why the emit still works?
#Emit('download')
private downloadAttachment(filename: string, attachmentId: string) {
return undefined;
}

The return undefined line is rather redundant because that's the default behavior in a function that has no return statement.
The return value of the #Emit-decorated function does not cause an undefined value to be emitted. When the function returns nothing (i.e., undefined), the original event arguments from the caller are emitted, which enables event forwarding. On the other hand, when the function returns any non-undefined value (even null), only that value is emitted.
So in your specific example, the <attachment-link>'s download-attachment event is forwarded as the download event from the component. The emitted event data would still be filename and attachmentId.

Related

Vue - Emit Child to parent

In a vue2 application i need to send a value from a child to the parent. I try something like this
Child function
goTo(id: string) {
this.$emit('goToSpots', id)
},
Parent Component
<Main
class="px-5"
#goToSpots="goToSpots()"
/>
Parent function
goToSpots(id: string) {
this.selected = id
},
The problem: this.selected returns undefined, can't get param properly.
The question is: Whats is the proper way to send params?
In your HTML template, use
#goToSpots="goToSpots($event)"
or
#goToSpots="goToSpots"
instead. At the moment, you are not passing any argument to the function. If you don't add brackets at all, the parameter is automatically added and if you use brackets, you can pass the reserved variable $event which contains the emitted data.

How could i get event type in vue when watch listener triggered?

I have created a wacther and when <Input v-model="computedData" /> changes the data, I can get the old and new values. Data can also be changed via XMLHttpRequest. I need to know who changed the data. I can't get event as parameter via watcher when data changes. Because there is no argument to get to event on whatcher. I know, I can access the event directly using event. But I also know it's deprecated. So I'm researching how the event type can be accessed as InputEvent nor XMLHttpRequest.
#Options({
name: 'dx-table',
watch: {
computedData: {
handler(newData: any, oldData: any) {
console.log(event); // is there any way to access `event` without using `event` directly
},
deep: true,
immediate: true,
},
},
})
export default class DxTable extends Vue.with(Props) {}
There is no way to get the event or cause of the data change in a watcher. A watcher is simply a function that executes whenever some reactive property changes and all you are given is the old value and the new value.
Based on the information given, there's two ways the data can be changed:
input event: Register a listener for the input event on the component, like #input="handleInput". The event object is passed to the function.
XMLHttpRequest: Wherever you are changing the property in code, just call a method to handle that specific mutation.
I don't know specifics about your code, but this might be a situation where instead of mutating the data freely throughout your code, use one or more "setter" methods to do this so that you know exactly where and how the data is being mutated. Using a watcher gives you no information about where or how the data changed, and if you're mutating the data in many random places in your codebase then you're going to have a difficult time trying to trace through your code to find the cause of the mutation.

Prop inheritance from component

Not too sure if i understand "Non-prop attributes" from manual (or vue.js at all): https://v2.vuejs.org/v2/guide/components-props.html
Say i have ChildComponent.vue file:
<template>
<input type="text" class="input" :value="childValue" v-on="listeners">
</template>
<script>
export default {
props: {
childValue: {
type: String,
default: 'blah',
}
},
computed: {
listeners() {
return {
// Pass all component listeners directly to input
...this.$listeners,
// Override input listener to work with v-model
input: event => this.$emit('input', event.target.value)
}
}
}
}
</script>
Then I add it to ParentComponent like this:
<template>
<ChildComponent v-model="parentValue" placeholder="default" #keydown.enter="parentMethod"/>
</template>
<script>
export default {
data () {
return {
parentValue: "",
};
},
methods: {
parentMethod () {
...
}
},
}
</script>
The flow should be (and works like this) - anything written to text field in ChildComponent after pressing enter should be sent all the way up to ParentComponent as parentValue and parentMethod() should be invoked.
If I understand correctly BasicComponent is kind of extension to its template's root component, meaning <input> will not only have props type and class set, but also placeholder (which has "default" value)?
Also, does this mean that the v-model prop to whom parentValue data is assigned will be propagated to <input> element as well, making my :value and v-on bind reduntant?
Another question - how the hell is v-on="listeners" working without specifying an event, does it mean i'm listening to EVERY event?
In the parent component there is a shorthand #keydown.enter which means it's listening for keydown.enter event, yet in listeners() method I'm emitting an input event...
I also have big trouble understanding what is going on in listeners() method at all, so any help in deciphering this will be greatly appreciated. :D
Thanks in advance for help.
Cheers
Let's do this one topic at a time...
Difference between props and non-prop attributes:
Props are the parameters which you define in your props object. With props you can tell the user what types they should use for a given prop, whether they're required or not, default values, assign validation functions, and etc.
Also, props are reactive, so if your template depends on a prop and the prop updates, so will your template.
Attributes you assign to your components, but do not correspond to any props, are passed to the $attrs variable. You can use it to access those values, like $attrs.id to get the id, or $attrs.name to get the name, and so on.
The event flow in your case:
Yes, the things you type on your ChildComponent are passed to ParentComponent. They are passed both via your v-model and via #keydown.enter="parentMethod".
You probably know how events work, but if you don't, here's the gist of it: When you need to pass data from a child component to a parent component, you emit an event in your child and listen to it in your parent.
For example, if you want to emit an event called foo, you would call $emit somewhere in your child, using $emit('foo'). Then, you'd listen to it in the parent by adding #foo="yourHandler" to the child, where yourHandler is a function written to handle the event. Which is what you did with #keydown.enter="parentMethod".
<input> will not only have props type and class set, but also placeholder (which has "default" value)?:
Answer: It depends. What the <input> tag in your template will receive depends on whether or not your root element (<input>) inherits component attributes. That behavior is defined by the inheritsAttrs property of a component, which defaults to true.
What that means is, in your case, since you haven't specified inheritsAttrs it will default to true, and yes, every attribute you pass to <ChildComponent> will be passed to your <input> tag, except for the things you defined manually.
Since you declared your <input> tag like this:
<input type="text" class="input" :value="childValue" v-on="listeners">
Your <input> tag will inherit all attributes from <ChildComponent> except type, value and your listeners (more on that later). The exceptions to that rule are class and style, which are always inherited regardless.
PS: Note that type, class and placeholder are attributes, not props.
Does this mean that the v-model prop to whom parentValue data is assigned will be propagated to element as well, making my :value and v-on bind reduntant?
Answer: No, but it also won't work. Here's why:
When you declare your listeners using this piece of code:
listeners() {
return {
// Pass all component listeners directly to input
...this.$listeners,
// Override input listener to work with v-model
input: event => this.$emit('input', event.target.value)
}
}
You are assigning to your listeners computed property every single event listener placed on your ChildComponent tag, including your keydown event, which is why it works.
The assignment is done in this line:
...this.$listeners,
It uses the spread operator to add all the elements in your $listeners variable (which holds all your component events) to the object you're returning.
The only event which you are not inheriting is input, as defined in this line:
input: event => this.$emit('input', event.target.value)
With that line, you tell your code that the behavior of your input event will be the one you defined, rather than the inherited.
Then, when you assign v-on="listeners" to your input, you're telling it to listen to every single event listed on your listeners variable. That is: You're appending all your inherited events and your custom input event to your input event.
Finally, to explain why it isn't redundant but why it won't work, you must understand how v-model works. It (usually) works by listening on the input event of a component, and using it to update the value prop of the same component. So in this line:
<ChildComponent v-model="parentValue" placeholder="default" #keydown.enter="parentMethod"/>
You are doing two things:
You're assigning the value of parentValue to the value prop of ChildComponent
You're telling your component to update parentValue whenever the input event is called.
That means that assigning a value and listeners to your input tag is not redundant, since you need it for v-model to work properly, but it won't work in the end, since your component doesn't have a value prop. it has a childValue prop instead.
To fix it, you have two options:
Rename childValue to value
Or tell your component to use childValue as model
To do the second approach, just append this piece of code to your ChildComponent:
model: {
prop: 'childValue',
event: 'input'
}
That will tell your component to use that prop and that event to make v-model work.
THE END
A final note: In the future, try narrowing your question down to a single topic. It will be easier to answer and will help people who search for those topics later on.

Why is "event" accessible in Vue v-on methods even without the argument?

According to the page on event handling in the docs for Vue, when you use v-on like v-on:click="handler" the handler function will automatically get the original DOM event as the first argument. This code snippet is directly adapted from those docs.
new Vue({
// Vue config shortened for brevity
methods: {
handler(event) {
// `this` inside methods points to the Vue instance
alert('Hello ' + this.name + '!')
// `event` is the native DOM event
if (event) {
alert(event.target.tagName)
}
}
}
})
Why the heck can I still access event even if I omit it from the functions parameter list like this:
handler() {
console.log(event); // Still returns the native DOM object even though
// I don't explicitly define `event` anywhere
}
Shouldn't event be undefined if I don't add it as an argument to the function?
I believe that'll be the global window.event:
https://developer.mozilla.org/en-US/docs/Web/API/Window/event
Nothing to do with Vue, it's just an unfortunate coincidence that you happened to call it event.
Maybe the docs explains the reason to use event in the handler function as first argument:
You should avoid using this property in new code, and should instead use the Event passed into the event handler function.
https://developer.mozilla.org/en-US/docs/Web/API/Window/event

Vue object passed through emit not recognized after listened in the other component

I have two component say componentA and componentB.
In componentA I have a click event that triggers a method that passed the user object as the argument, and this object is then emitted using the event bus. When I successfully listened to the emit in componentB I have assigned the object that was emitted to a variable, I console.log out the variable it showed the object, but when I console.log again the variable outside of the Event.$once the object disappeared.
Any help would be appreciated :)
Thanks!
Here's my code:
componentA
showUserStats(user) {
EventBus.$emit('userInfo', user);
this.$router.push({
name: 'componentB',
params: { id: user._id }
})
}
componentB
created() {
EventBus.$once('userInfo', (user) => {
this.userInfo = user
console.log('userInfo', this.userInfo);
});
console.log('userInfo outside EventBus', this.userInfo);
}
Here is the image of the output
This is happening because you're console logging on created lifecycle event. The console log outside of the $once call is called immediately on component creation (when the user info isn't assigned) but the console.log inside the $once is happening only when the event is emitted and in turn the varible / data is assigned.
In line with comments: You can't just throw a delay in and expect it to work as it's based on events firing. You've not given us info on when showUserStats is called so I don't know when this event emits.
With regards to the computed value, that should work as it'll update when the user info is assigned but you'll need to add a check for when it isn't so, something like this:
computed: {
fullName () {
return this.userInfo ? this.userInfo.name + ' ' + this.userInfo.surname : ''
}
}
It'll show blank unless the user info object has been assigned (note: You might need to alter the check based on how you initialise the userInfo var in data ())