Call parent methods from child components (Vue.js) - javascript

I'm doing a project and I need to call parent methods from child components. How can this be accomplished in Vue.js?

You should use this.$emit('myEvent') inside of your child component, when you want to trigger the method in the parent.
Then find your child component in the template of the parent and add an event catcher on it like this:
<template>
<your-child-component #myEvent="myMethod"/>
</template>
If you want to add parameters to your method, you can add a second parameter to your emit like this:
this.$emit("myEvent", "My parameter")
For this to work you don't have to change anything in the event "catcher", as long as the method you call has a parameter.

Maybe working example will make it more clear.
https://m-vue-leaflet.netlify.app/
code- https://github.com/manojkmishra/vue-leaflet-mapping
So here if you see there are 3 vue files in components folder.
Brew.vue is parent component to BrewList.vue child component.
Brew.vue- Parent Component
BrewList.vue - Child Component
Child component BrewList.vue is using emit to send mouse-over-brew & mouse-leave-brew values to parent Brew.vue. Also, in case you are interested Brew.vue parent is sending brew prop to BrewList.vue child.
As per docs-
https://v2.vuejs.org/v2/guide/components.html#Listening-to-Child-Components-Events

Dec, 2021 Update:
It works with $emit. The name of #callTest in parent component must be same as the name of $emit('callTest') in child component.
Parent Component:
<template>
<Child
#callTest="test" // Assign 'test' method to #callTest
/>
</template>
<script>
import Child from "../components/Child.vue";
import { defineComponent } from "vue";
export default defineComponent({
name: "Parent",
components: {
Child,
},
methods: {
test() {
alert("Test");
},
}
});
</script>
Child Component:
<template>
<button #click="$emit('callTest')">Click Me</button>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "Child",
});
</script>
Again, the name of #callTest in parent component must be same as the name of $emit('callTest') in child component.
If you use $emit in script section, this is needed different from template section.
Child Component:
<template>
<button #click="message">Click Me</button>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "Child",
methods: {
message() {
this.$emit('callTest') // 'this' is needed.
}
}
});
</script>
If test method has 2 parameters, you need to call test method with 2 arguments in child component like below.
Parent Component:
<template>
<Child
#callTest="test" // Assign 'test' method to #callTest
/>
</template>
<script>
import Child from "../components/Child.vue";
import { defineComponent } from "vue";
export default defineComponent({
name: "Parent",
omponents: {
Child,
},
methods: {
test(num1, num2) { // 'test' method has 2 parameters.
alert(num1 + num2);
},
}
});
</script>
Child Component:
<template> // Call 'test' method with 2 arguments.
<button #click="$emit('callTest', 3, 5)">Click Me</button>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
name: "Child",
});
</script>

Ideally, this is the right way to do so:
https://v2.vuejs.org/v2/guide/components.html#Listening-to-Child-Components-Events
On the other hand, I believe in your scenario (which I'm trying to assume cause it's not really clear), you can use this.$parent.methodName.
Keep in mind that the second suggestion is less clean. It should be used just in case of need.

So basically, there are 2 ways to answer your question
Using $emit, with syntax is #
Passing function as props, with syntax is : The same as your example
If you based on Vue docs and a lot of other Vue tutorials, you will see that they encourage people to use $emit event rather than passing function as props (the way you are using). The docs you can read here.
https://v2.vuejs.org/v2/guide/components-custom-events.html
https://v2.vuejs.org/v2/guide/components.html#Emitting-a-Value-With-an-Event
https://code.tutsplus.com/tutorials/design-patterns-for-communication-between-vuejs-component--cms-32354
vue, emitting vs passing function as props
The reason is Vue philosophy is passing props down, emitting events up. Using $emit will help to mark the function triggered as a Vue event, and therefore you can use global event listener. This will also may help you to separate between data flow logic and event flow logic.
However, using function as props is not wrong, and in fact, it can be used to achieve the same result. In my preference, I use the 2nd way when I write a component that has a default function, and the function is only overridden when parents pass another one. This will help me avoid rewriting default functions many times.
For the rest of the other cases, I will use the 1st way $emit.

Parent
<complited v-on:passData="fromChild" />
methods: {
fromChild(data) {
if (data.methodCall) return this[data.methodCall]();
}
aFunction() {
alert('function: a');
}
bFunction() {
alert('function: b');
}
}
Child
<template>
<div>
<button #click="parentCall()">Call Parent Function
</button>
</div>
</template>
methods: {
parentCall() {
this.$emit("passData", {methodCall: 'aFunction' });
}
}

I did this with props.passed the parent method through props to the child component. and accessed from the child component.
in child component
props: ["lesson","fetchLessons"],
and accessed props like this in child component
this.fetchLessons();
parent component
<InstructorLesson v-for="(lesson,index) in getFechedLessons" :lesson="lesson" :fetchLessons = "fetchLessons" v-bind:key="index"/>

Related

vue 3 access props passed from parent component that are not defined in child component

hope everything is okay.
i am used to react.js but when i try vue things were a bit different
in react it's very simple accessing props passed from parent in the child component
but in vue i must define each prop to use it.
so i was just wondering if it's possible to pass any prop and use it in the child component without defining it
in react i can do that
// parent component
const Parent = () => {
return (
<Child anyprop="propvalue" />
)
}
const Child = (props) => {
return (
<p>{JSON.stringify(props)}</p>
)
}
and that would work
in vue i can only use props if i define it like this
//parent
<template>
<Child anyprop="value" anotherprop="value"></Child>
</template>
<script setup>
const props = defineProps({
anyprop: String
})
//child
<template>
<p>{{props}}</p>
</template>
<script setup>
const props = defineProps({
anyprop: String
})
</script>
if i do that in vue i can only see "anyprop" not the "anotherprop" i must define it in the define props block to use it
any ideas what can i do to achieve something like what react.js offers
All data that isn't defined in props goes to attributes. Despite the name, they aren't HTML attributes and can have non-string values.
In a template they are available as $attrs:
<Child :anyprop="$attrs.anyprop" anotherprop="value"/>
A more common case is to pass all attributes instead of enumerating them, this is a counterpart to React {...rest}:
<Child v-bind="$attrs" anotherprop="value"/>
This may be not needed for root element like Child because attribute fallthrough is done by default, otherwise inheritAttrs: false should be used.
This requires Child to have anyprop prop declared.
In a script, attributes are available as context.attrs in setup function and useAttrs in script setup, they can be used to compute props for nested elements.

Vue functional component throws _c is not defined error

I created the following functional component using render method:
import Vue from "vue"
const { render, staticRenderFns } = Vue.compile(`<div>Hello World</div>`)
Vue.component("HelloWorld", {
functional: true,
render,
staticRenderFns
})
And then in App.vue:
<template>
<div id="app">
<HelloWorld />
</div>
</template>
<script>
export default {
data() {
return {
compiled: false
}
}
}
</script>
<style>
</style>
And I get the error:
_c is not defined.
Am I doing something wrong here?
As far as I'm aware, the render functions generated by Vue.compile cannot be used to render a functional component.
I think the closest thing to creating a functional component from a template string would involve parsing the string to get the element type and attributes and render as:
Vue.component("hello-world", {
functional: true,
render: function(createElement, context) {
return createElement(
"div",
'example text'
);
}
});
As Decade Moon already mentioned, render functions returned by Vue.compile cannot be used as render function in functional component. Reason for that becomes clear when you inspect the signature of the function returned by Vue.compile:
const render: (createElement: any) => VNode
As you can see, function is missing second argument, which is required for render functions of functional components:
render: (createElement: CreateElement, context: RenderContext<Record<never, any>>) => VNode
Functional components are instanceless. That means no this context - that's why additional context parameter is needed to carry props\listeners etc.
Also if you look at this post on Vue forum:
The render function created by compile() relies on private properties of the component. To have access to those properties, the method has to be assigned to a property of the component(so it has access to those properties via this)
However that doesn't mean you can't create functional component with template. You can, you are just not able to use Vue.compile to pass template text dynamically. If you are fine with static template, you can do it like this:
// component.vue
<template functional>
<div>Hello World</div>
</template>
<script>
export default {
name: "hello"
};
</script>
...and use the component like any other SFC (single file component = VUE file)
If you need dynamic template text, just use non-functional component instead...

How to foward $refs in Vue

I have a component which should pass everything on to the child. I'm successfully passing $attrs and $listeners already:
<template>
<el-form v-on="$listeners" v-bind="$attrs" :label-position="labelPosition">
<slot />
</el-form>
</template>
But I'm unsure how to also forward $refs like we can do in React, so that when using my component like this:
<el-form-responsive
class="form"
:model="formValues"
status-icon
:rules="rules"
ref="form"
label-width="auto"
#submit.native.prevent="submitForm"
>
Then this.$refs.form is actually a reference to the child <el-form>.
I would rather do this transparently, as in you pass exactly the same props to el-form-responsive as you would to a el-form without needing to know that refs has to be passed in a special way.
I don't think it is possible to directly mimic React's ref. A ref attribute in Vue is just a string which is used to register a child component reference to the parent's $refs object during the render function.
Here are the links to documentations doc & doc
So basically it's a kind of inverted logic.. instead of passing a ref to a child in Vue we get it from the child into the parent. So it's not really possible at this point to create a grandchild reference, which is what you need.
There are some workarounds though.
1. Quick dirty and not transparent but technically it would work:
In the parent component, which uses your el-form-responsive, on mounted hook we could replace the original child reference with the grandchild ref.
Your el-form-responsive component. Template:
<el-form ref="elform">
A parent which uses your el-form-responsive. Template:
<el-form-responsive ref="form">
Script:
...
mounted () {
this.$refs.form = this.$refs.form.$refs.elform
}
And after this this.$refs.form is actually a reference to the granchild <el-form>
2. This one would be more elaborate, but probably mach better then the first method:
In order to make the el-form-responsive component really transparent you could expose some of the methods and properties from the child el-form component to any potential parent. Something like this:
el-form-responsive. Template:
<el-form ref="elform">
Script:
export default {
data: () => ({
whatever: null
}),
mounted () {
this.whatever = this.$refs.elform.whatever
},
methods: {
submit () {
this.$refs.elform.submit()
}
}
}
So then inside some parent el-form-responsive could be used like this:
<el-form-responsive ref="form">
...
mounted () {
const formWhatever = this.$refs.form.whatever // actually `whatever` from `el-form`
this.$refs.form.submit() // eventually calls submit on `el-form`
},
If you are working with a custom component with the script setup in Vue 3, it's worth nothing that template refs work like this.
Essentially, you will have to use defineExpose to "expose" your child component data to the parent component
Try this to replace parent's ref with child's , In el-form-responsive
<template>
<el-form v-on="$listeners" v-bind="$attrs" :label-position="labelPosition" ref='ref'>
<slot />
</el-form>
</template>
mounted () {
Object.entries(this.$parent.$refs).forEach(([key, value]) => {
if (value === this) {
this.$parent.$refs[key] = this.$refs.ref
}
})
...
It is work for me:
mounted() {
Object.assign(Object.getPrototypeOf(this), this.$refs.formRef)
}
warning: insert to __proto__,maybe is bad way

Changing object from external file

I am learning Vue and before that I have some experience working with React.
As, I move on comprehending the basics of vue, I sort of compare things with JS
<template>
<div id="app">
<HelloWorld msg="Below This we will inclue our To-do task list"/>
<p>{{msg}}</p>
<Todos :toDoItem="todos"/>
</div>
</template>
import Todos from "./components/todo.vue";
let todo =
{
name: "Rohit",
title: "Full Stack Developer",
completed: true
}
export default {
name: "app",
components: {
HelloWorld,
Todos
},
data() {
return {
msg: "hello",
todos: todo
};
}
};
We are passing the props here to child component and changing it using method
<template>
<div>
<div class="main-class">
<p>{{toDoItem.title}}</p>
<input type="checkbox" v-on:change="markComplete">
</div>
</div>
</template>
<script>
export default {
name: "toDoItem",
props: {
toDoItem: {
type: Object
}
},
method: {
markComplete() {
this.toDoItem.complete = !this.toDoItem.complete
}
};
</script>
This is where I am unable to comprehend many things.
Question 1: Based on my understanding of this, shouldn't it point to a global space and hence this should be undefined?
markComplete() {
this.toDoItem.complete = !this.toDoItem.complete
}
Question 2: In react we can't change props passed to a child object probably? but here we are changing the props? Also, In general, if we declare a object in another file (app.js), can we change it to in another file (someApp.js)?
All methods in a Vue instance are bound to the instance so that this works properly.
You should not be changing props. Here, you are changing a member of a prop -- toDoItem is a prop, and complete is a member of it -- which is not controlled, but you still should not do it. You should instead emit an event that the parent handles.
Your markComplete should be in the parent, since it owns the data being manipulated. It would look like:
markComplete() {
this.todos.complete = !this.todos.complete;
}
In the child, the method would be something like
toggleComplete() {
this.$emit('toggle');
}
And the parent would use v-on to handle the toggle event:
<Todos :toDoItem="todos" v-on:toggle="markComplete"/>
This way all the code that manipulates data owned by a component happens within that component. Nobody calls anybody else's methods or modifies their data.
I think you intend for todos to be an array of items, and probably want to v-for over them, having one child component for each item, but that's another issue.
I cannot answer your last question about objects declared in different files, because I don't understand it. It is not unusual to import objects and modify them, generally. As I mentioned, though, Vue components should be the only ones to modify their own data.

emit event with arguments from child to parent with ember 3.6

I would like emit one event from child to parent but with some argument.
Child component javascript
import Component from '#ember/component';
export default Component.extend({
tagName: 'li',
actions: {
favoriteWasClicked() {
const organization = this.get('organization');
// organization.id value equal to 'facebook' here in my app
// we're gonna send its id to parent component "orgs" like an argument of clicked method
this.clicked(organization);
}
}
});
Parent component .hbs template
{{child-component
clicked=(action "favoriteClicked" value="organization.id")
}}
Parent component javascript controller file
import Controller from '#ember/controller';
export default Controller.extend({
actions: {
favoriteClicked(orgId) {
console.log(orgId);
}
}
});
I'm getting undefined in console.log(orgId); Why? What am i missing
Thank you!
You simply need to change organization.id to just id. What I mean is; you need to do the following:
{{child-component
clicked=(action "favoriteClicked" value="id")
}}
You send the event from the child component with an organization; which simply contains an id; but not organization.id; so it must be just id for the value property in the action passing within parent's template.
I prepared an ember twiddle for you to illustrate the solution I proposed in action. Hope that helps.

Categories

Resources