I have just started using Vue and experienced some unexpected behavior. On passing props from a parent to child component, I was able to access the prop in the child's template, but not the child's script. However, when I used the v-if directive in the parents template (master div), I was able to access the prop in both the child script and child template. I would be grateful for some explanation here, is there a better was of structuring this code? See below code. Thanks.
Parent Component:
<template>
<div v-if="message">
<p>
{{ message.body }}
</p>
<answers :message="message" ></answers>
</div>
</template>
<script>
import Answers from './Answers';
export default {
components: {
answers: Answers
},
data(){
return {
message:""
}
},
created() {
axios.get('/message/'+this.$route.params.id)
.then(response => this.message = response.data.message);
}
}
</script>
Child Component
<template>
<div class="">
<h1>{{ message.id }}</h1> // works in both cases
<ul>
<li v-for="answer in answers" :key="answer.id">
<span>{{ answer.body }}</span>
</li>
</ul>
</div>
</template>
<script>
export default{
props:['message'],
data(){
return {
answers:[]
}
},
created(){
axios.get('/answers/'+this.message.id) //only worls with v-if in parent template wrapper
.then(response => this.answers = response.data.answers);
}
}
</script>
this.message.id only works with v-if because sometimes message is not an object.
The call that you are making in your parent component that retrieves the message object is asynchronous. That means the call is not finished before your child component loads. So when your child component loads, message="". That is not an object with an id property. When message="" and you try to execute this.message.id you get an error because there is no id property of string.
You could continue to use v-if, which is probably best, or prevent the ajax call in your child component from executing when message is not an object while moving it to updated.
Related
Currently trying to use a method belonging to the parent
<p class="message-date text-center">
{{ $emit('format_date_day_month_year_time', message.date) }}
</p>
However I am getting the error.
Converting circular structure to JSON
--> starting at object with constructor 'Object'
How can I call a function inside a child component that does not rely on an event? I apologize for asking such a simple question but everything I was able to find on google is using $emit and using an event.
$emit was designed to only trigger an event on the current instance of vue. Therefore, it is not possible to receive data from another component this way.
For your case, I would suggest to use Mixins especially if you need to use certain functions among multiple vue components.
Alternately, let the child component call the the parent through $emit then receive the result from the parent through a prop.
Your code could be something as follows:
Child component
<template>
<p class="message-date text-center">
{{ date }}
</p>
</template>
<script>
export default {
name: 'Child',
props: {
date: String,
},
mounted() {
this.$emit("format-date", message.date);
},
}
</script>
Parent component
<template>
<Child :date="childDate" #format-date="formatChildDate" />
</template>
<script>
import Child from '#/components/Child';
export default {
components: {
Child,
},
data: () => ({
childDate: '',
}),
methods: {
formatChildDate(date) {
this.childDate = this.formatDateDayMonthYearTime(date)
},
formatDateDayMonthYearTime(date) {
//return the formatted date
},
},
}
</script>
with $emit you call a function where the Parent can listento.
where you are using it i would suggest a computed prop of the function.
But back to your Question here is a example of emiting and listen.
//Parent
<template>
<MyComponent #childFunction="doSomethingInParent($event)"/>
</template>
//Child
<template>
<button #click="emitStuff">
</template>
.....
methods:{
emitStuff(){
this.$emit(childFunction, somedata)
}
with the event you can give Data informations to a Parentcomponent.
for some reason emited event doesn't handles by parent component
HTML:
<template id="parent-template">
<div>
<h1>Parent: {{message}}</h1>
<child-component message="Child message"></child-component>
</div>
</template>
<template id="child-template">
<div>
<h2>Child: {{message}}</h2>
<button v-on:click="changeMessage('Changed')">Change</button>
</div>
</template>
<div id="app">
<parent-component message="Parent message"></parent-component>
</div>
JS (es5):
Child:
Vue.component("child-component", {
template: "#child-template",
props:['message'],
methods:{
changeMessage: function(newMessage){
this.message = newMessage;
this.$emit("message-changed", newMessage);
}
}
});
Parent:
Vue.component("parent-component", {
template: "#parent-template",
props:['message'],
mounted: function(){
var v = this;
this.on("message-changed", function(newValue){
alert("Emit handled!");
v.message = newValue;
});
}
});
So, everythings looks fine, but nothing happens when event fires. Why?
You cannot check for the emitted event on a mounted function, since the child Vue instances are not instantiated at that point. If you want to run the code AFTER everything has been rendered which is what I am assuming you are after then you need to run the code after a tick.
this.$nextTick(function () { // Your code goes here }
Also, for clarity, I would normally do a v-on:message-changed="parentMethod()" inside of the HTML. That way the parent is not tightly coupled to the child component at the mounted.
<child-component v-on:message-changed="parentMethod()"> </child-component>
Below is the Vue Documentation regarding the mounted information I provided:
https://v2.vuejs.org/v2/api/#mounted
This is an easy question but I dont know the best way to do it correctly with Vue2:
Parent Component:
<template>
<div class="parent">
<child></child>
{{value}} //How to get it
</div>
</template>
Child Component:
<template>
<div class="child">
<p>This {{value}} is 123</p>
</div>
</template>
<script>
export default {
data: function () {
return { value: 123 }
}
}
</script>
Some ways you can achieve this include:
You can access the child VM directly via a ref, but this gets hairy because the ref won't be populated until the child component has mounted, and $refs isn't reactive, meaning you'd have to do something hacky like this:
<template>
<div class="parent">
<child ref="child"></child>
<template v-if="mounted">{{ $refs.child.value }}</template>
</div>
</template>
data() {
return {
mounted: false
}
},
mounted() {
this.mounted = true;
}
$emit the value from within the child component so that the parent can obtain the value when it is ready or whenever it changes.
Use Vuex (or similar principles) to share state across components.
Redesign the components so that the data you want is owned by the parent component and is passed down to the child via a prop binding.
(Advanced) Use portal-vue plugin when you want to have fragments of a component's template to exist elsewhere in the DOM.
Child components pass data back up the hierarchy by emitting events.
For example, in the parent, listen for an event and add the child value to a local one (an array for multiple child elements would be prudent), eg
<child #ready="childVal => { value.push(childVal) }"></child>
and in the child component, $emit the event on creation / mounting, eg
mounted () {
this.$emit('ready', this.value)
}
Demo ~ https://jsfiddle.net/p2jojsrn/
I have a list of items that don't get created until after an async call happens. I need to be able to get the getBoundingClientRect() of the first (or any) of the created items.
Take this code for instance:
<template>
<div v-if="loaded">
<div ref="myItems">
<div v-for="item in items">
<div>{{ item.name }}</div>
</div>
</div>
</div>
<div v-else>
Loading...
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
items: []
}
},
created() {
axios.get('/url/with/some/data.json').then((response) => {
this.items = response.data;
this.loaded = true;
}, (error) => {
console.log('unable to load items');
});
},
mounted() {
// $refs is empty here
console.log(this.$refs);
// this.$refs.myItems is undefined
}
};
</script>
So, I'm trying to access the myItems ref in the mounted() method, but the this.$refs is empty {} at this point. So, therefore, I tried using a component watch, and various other methods to determine when I can read the ref value, but have been unsuccessful.
Anyone able to lead me in the right direction?
As always, thanks again!!
UPDATES
Added a this.$watch in the mounted() method and the $refs still come back as {}. I then added the updated() method to the code, then was able to access $refs there and it seemed to work. But, I don't know if this is the correct solution?
How does vuejs normally handle something like dynamically moving a div to an on-screen position based on async data? This is similar to what I'm trying to do, grab an element on screen once it has been rendered first (if it even should be rendered at all based on the async data), then access it to do something with it (move it to a position)?
Instead of doing on this.$refs.myItems during mounted, you can do it after the axios promise returns the the response.
you also update items and loaded, sou if you want to use watch, you can use those
A little late, maybe it helps someone.
The problem is, you're using v-if, which means the element with ref="myItems" doesn't exist yet. In your code this only happens when Axios resolves i.e. this.loaded.
A better approach would be to use a v-show.
<template>
<div>
<div v-show="loaded">
<div ref="myItems">
<div v-if="loaded">
<div v-for="item in items">
<div>{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div v-show="!loaded">
Loading...
</div>
</div>
</template>
The difference is that an element with v-show will always be rendered and remain in the DOM; v-show only toggles the display CSS property of the element.
https://v2.vuejs.org/v2/guide/conditional.html#v-show
My first component (child 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.$parent.$options.methods.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 parent component (second component)
My second component (parent 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
test: null
}
},
methods: {
createImage(item, response) {
console.log(item)
this.$set(this.clicked, item, true)
this.test = item
},
}
}
</script>
If the code executed, it success call createImage method on parent component. The console log display value of item
But my problem is the data on parent component not success updated
How can I solve this problem?
You really should get in the habit of using events instead of directly accessing the parent component from a child.
In your case, it would be simple to emit an event in the then handler of the child's async request:
.then((response) => {
this.$emit('imageAdded', item);
});
And listen for it in the parent scope:
<child-component #itemAdded="createImage"></child-component>
That way, any component that uses that child component can react to its imageAdded event. Plus, you won't ever need to spend time debugging why the createImage method is firing when it's never being called in the Vue instance.
Your code isn't working because the way you are invoking the createImage method means that the this inside the function will not be referencing the parent component instance when it is called. So setting this.clicked or this.test will not affect the parent instance's data.
To call the parent component's function with the right context, you would need to do this:
this.$parent.createImage(item, response)