Vuejs mount the child components only after data has been loaded - javascript

What am trying to achieve is to pass data as props in my children components but this data is loaded from the server so it takes a while to load.
I would now like to only mount the children components when the data is fully loaded
SO currently am doing this
IN the parent component
<template>
<child-cmp :value="childdata"></child-cmp>
</template>
<script>
export default{
data(){
childdata : [];
},
methods:{
fetchINitData(){
//fetch from server then
this.childdata = res.data.items;
console.log(res.data.items) //has some values
}
}
components:{
childcmp
},
mounted(){
this.fetchINitData();
}
}
</script>
NOw in my child component
<script>
export default{
props:["value];
mounted(){
console.log(this.value) //this is always empty
}
}
</script>
As from the above example the data passed as props is always empty on the children component. How do i only mount the child component after data has been received or how do i ensure that the childs component get the latest data changed.

Use <template v-if="childDataLoaded">,And load your child component after getting data like this
<template>
<template v-if="childDataLoaded">
<child-cmp :value="childdata"></child-cmp>
</template>
</template>
<script>
export default{
data(){
childDataLoaded: false,
childdata : [];
},
methods:{
fetchINitData(){
//fetch from server then
this.childdata = res.data.items;
this.childDataLoaded = true;
console.log(res.data.items) //has some values
}
}
components:{
childcmp
},
mounted(){
this.fetchINitData();
}
}
</script>
Here is the Nice and cleaner way to update child component.
var child = Vue.extend({
template: "<div>Child Component : {{name}} <div v-if='loading'>Loading...</div></div>",
props: ['name','loading']
});
var app = new Vue({
el: "#vue-instance",
data: {
name: "Niklesh",
loading: true
},
mounted() {
var vm = this;
setTimeout(function() {
vm.name = "Raut";
vm.loading = false;
}, 1000);
},
components: {
child
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.1/vue.js"></script>
<div id="vue-instance">
<child :name="name" :loading="loading"></child>
</div>

Just Bind key to child-cmp you get reactive output. this is simplest method of reactivity in Vue js.
<template>
<child-cmp :key="childdata" :value="childdata"></child-cmp>
</template>
<script>
export default{
data(){
childdata : [];
},
methods:{
fetchINitData(){
//fetch from server then
this.childdata = res.data.items;
console.log(res.data.items) //has some values
}
}
components:{
childcmp
},
mounted(){
this.fetchINitData();
}
}
</script>

Related

Vue2 use $ref to load data from child component and put into parent component's data

In Vue2 I'm trying to access child components' data and then put into parent component's data without triggering an event. In the following example I want to save count:20 into parent component, please tell me if there's any mistake, thanks!
Child Component
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
};
</script>
Parent Component
<template>
<div>
<child ref="child1"></child>
{{count}}
</div>
</template>
<script> import child from './child.vue'
export default {
components: {
child
},
data() {
return{
count:this.$refs.child1.count
}
},
}
</script>
warn message in VScode
Property 'count' does not exist on type 'Vue | Element | Vue[] | Element[]'.
Property 'count' does not exist on type 'Vue'.
warn message in browser
[Vue warn]: Error in data(): "TypeError: undefined is not an object (evaluating 'this.$refs.child1')"
Let me preface with I would recommend using the Vue framework as intended. So passing data from a child to the parent should be done with $emit or using a vuex store for centralized state management.
With that out of the way you will want to wait until the parent component is mounted to set the count data attribute.
Child
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
};
</script>
Parent
<template>
<div>
<child ref="child1"></child>
{{ count }}
</div>
</template>
<script>
import Child from "./components/Child";
export default {
components: {
Child
},
data() {
return{
count: 0
}
},
mounted () {
this.count = this.$refs.child1.count
}
};
</script>
This will work, however it WILL NOT BE reactive. This can all be greatly simplified AND made reactive with the following changes:
Child
<template>
<div></div>
</template>
<script>
export default {
data() {
return {
count: 20,
};
},
watch: {
count (currentValue) {
this.$emit('update', currentValue);
}
},
beforeMount () {
this.$emit('update', this.count)
}
};
</script>
Parent
<template>
<div>
<child #update="count = $event"></child>
{{ count }}
</div>
</template>
<script>
import Child from "./components/Child";
export default {
components: {
Child
},
data() {
return{
count: 0
}
}
};
</script>
Quick link to show a working example: https://codesandbox.io/s/interesting-kalam-et0b3?file=/src/App.vue

Bootstrap-Vue Table not updating with table.refresh()

I have a table that should be changing its data constantly. When a button is clicked in the parent component, a call is made to the server and json file is then loaded into a child component(The table) through a prop.
Whenever a different button is clicked and the table needs to reload the data, it doesnt. I have tried doing:
this.$refs.dmTable.refreshTable();
and
this.$forceUpdate()
Rough structure of my code
Parent.vue
<template>
<Button getData("this")>Get This Data</Button>
<Button getData("that")>Get ThatData</Button>
<MyTable v-if="showTable" :data="data" />
<template>
<script>
export default {
data(){
return{
showTable:false,
data: null
}
},
methods:{
getData(dataType){
getDataFromServer(dataType).then(data =>{
this.data = data.body
this.showTable = true
})
}
}
}
</script>
MyTable.vue
<template>
<b-table :items="data"><b-table/>
</template>
<script>
export default{
props: ["data"]
}
</script>
If you click the first button, the data loads fine into the table. But if you then click the second button and try to load new data into the table nothing happens. I tried creating a method called updateTable() within the child component that contains this.$refs.myTable.update() but it does nothing.
Edit: One thing to note about this, the data that I am loading onto this table is quite large, 5mb json file.
The actual function that gets called:
showTableView(model, type) {
request
.get(
`/table_files/${this.folder_name}/${model}.json`
)
.then(response => {
this.type = type;
this.current_model = model;
if (type === "joins") {
this.tlorderData = response.body.fields.joins;
this.show_joins_table = true;
this.showTable = false;
this.refreshTable()
return false; // MAYBE RETURNING FALSE BREAKS THE RERENDERING?
}
else if (type === "dimension_groups") {
this.show_joins_table = false;
this.showTable = true;
this.tlorderData = response.body.fields.dimension_groups;
this.refreshTable()
return false;
}
})
.catch(err => {
this.response_error = err;
});
},
I don't see where you are defining data and showTable in your main app component.setting this.data to a value is not reactive (it is creating a non-reactive property on the app component).
Try this:
<template>
<Button #click="getData('this')">Get This Data</Button>
<Button #click="getData('that')">Get ThatData</Button>
<MyTable v-if="showTable" :data="data" />
<template>
<script>
export default {
data() {
return {
data: [],
showTable: false
}
},
methods:{
getData(dataType){
getDataFromServer(dataType).then(data =>{
this.data = data.body
this.showTable = true
})
}
}
}
</script>
The data() section will define data and showTable as reactive properties on your app/component instance.
The problem was in something that I did not mention in my code.
The way I was loading in data into the table was like this:
<template>
<b-table :items="reData"><b-table/>
</template>
<script>
export default{
props: ["data"],
data(){
return{
reData: this.data
}
}
}
</script>
This prevented my table from updating whenever data gets changed in the prop, note that I am passing in my prop to data() then passing THAT into my table. So the prop would change but the actual data that the table was showing would not.
Proper Way to pass in from props to table to prevent this from happening:
<template>
<b-table :items="data"><b-table/>
</template>
<script>
export default{
props: ["data"]
}
</script>

How to pass data to a component template

Currently I'm defining several component template(s) within a single vue component. I've defined some as strings, however it makes it somewhat incomprehensible if it gets more complex. So instead I'm making it return a separate component as a template. However I'm not sure how to pass data to the component.
This is a sample of the current approach for one of my component templates within my vue component. It's returning the template as a string and renders the html.
progessTemplate: function () {
return {
template: ('progessTemplate', {
template: `
<div id="myProgress" class="pbar">
<div id="myBar" :class="barColor" :style="{'width': width}">
<div id="label" class="barlabel" v-html=width ></div>
</div>
</div>`,
data: function () {
return { data: {} };
},
computed: {
width: function () {
if (this.data.SLA <= 20) {
this.data.SLA += 20;
}
return this.data.SLA + '%';
},
barColor: function(){
if(this.data.SLA > 60 && this.data.SLA <= 80){
return 'bar progressWarning';
}else if(this.data.SLA > 80){
return 'bar progressUrgent';
}
}
}
})
}
},
I'd like to avoid this approach and call a separate file instead.
I import the component into my vue file
import QueryDetailTemplate from '../../ej2/queryDetailTemplate';
and within my main vue file I have this function 'QueryDetailTemplate':
export default{
data(){
return{
data: [...],
QueryDetailTemplate: function(){
return {
template: QueryDetailTemplate,
props:{
test: 'Hello World',
},
};
},//end of QueryDetailTemplate
}//end of data
...
}
In my QueryDetailTemplate.vue this is my code:
<template>
<div>
Heyy {{test}} //undefined
</div>
</template>
<script>
export default{
props: ['test'],
created(){
console.log(this.test); //shows undefined
}
}
</script>
It renders the 'Heyy' that's hardcoded but it doesn't get the 'test' prop.
Appreciate any pointers
I'm not quite sure what you're trying to achieve but...you should be specifying the component as a component like so:
export default{
components: {
QueryDetailTemplate
},
data(){
return{
data: [...],
}
}
OR if you want to import it asynchronously:
import Vue from 'vue'
export default {
methods: {
import() {
Vue.component(componentName, () => import(`./${componentName}.vue`));
}
}
}
And then you can render it in main:
<query-detail-template
test='Hello World'>
</query-detail-template>

change parent props from child component in vuejs2

I want to change the parent's prop's value from a child component. This works great in vuejs 1 but not in vue 2 (I want to use it in vue.js 2).
Here is a small example :
HTML
<div id="app">
<parent :content="{value:'hello parent'}"><</parent>
</div>
JavaScript
var parent = {
template: '<child :content="content"></child>',
props: ['content'],
};
var child = {
template: '<div>{{ content.value }}<button #click="change">change me</button></div>',
props: ['content'],
methods: {
change() {
this.content.value = "Value changed !";
}
}
};
Vue.component('child', child);
Vue.component('parent', parent);
new Vue({
el: '#app',
});
https://jsfiddle.net/f5gt94f2/
tl;dr: in vue2, you need to use the .sync modifier.
Create a local copy of the content prop in the parent's data (see reason here).
var parent = {
...
data() {
return {
localContent: this.content // creating a local copy, so we can mutate and react to it
}
}
};
Now, pass that localContent to the child, not content. And pass it using .sync so it can be updated:
var parent = {
template: '<div><child :content.sync="localContent"></child></div>',
... // ^^^^^^^^^^^^^^^^^^^^-- changed here
Now, in the child, don't assign to this.content.value, emit an update event instead:
var child = {
...
change() {
this.$emit('update:content', {value: "Value changed !"})
}
}
};
This event, with the new value, will be picked up by the parent and will update its localContent which also will, in consequence, update the child's content prop.
Final running code below.
var parent = {
template: '<div><child :content.sync="localContent"></child><br>At parent: {{ localContent }}</div>',
props: ['content'],
data() {
return {
localContent: this.content
}
}
};
var child = {
template: '<div>At child: {{ content.value }}<button #click="change">change me</button></div>',
props: ['content'],
methods: {
change() {
this.$emit('update:content', {value: "Value changed !"})
}
}
};
Vue.component('child', child);
Vue.component('parent', parent);
new Vue({
el: '#app'
});
<script src="https://unpkg.com/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<parent :content="{value:'hello parent'}"></parent>
</div>
You will have to use emit events for this
Parent:
<child :content="content" #updateParent="updateValue"></child>
methods: {
updateValue (value) {
// Your code here
}
}
Child:
props: ['content'],
methods: {
change () {
this.$emit('updateParent', value)
}
}
https://v2.vuejs.org/v2/guide/components.html#Custom-Events

How to broadcast to all components from root Vue.js

I want to $emit some data to all child components in vue.js 2 and this is my code :
Root Component
const app = new Vue({
created: function(){
// here is some promise and after it done the below line runs
this.$emit('foo', 'bar');
}
});
Child Component
<template></template>
<script>
export default {
created: function() {
this.$on('foo', function(data) {
console.log(data);
});
}
}
<script>
It's not working.
Is there any way to broadcast some data to all child components from root?
Vuejs is bottom up approach, means child component is compiled first and ask for this.$on so broadcasting will not work here with $emit-$on
Use props in child component to watch root data changes, giving this example where child1 and child2 having same root component data named name
var child1 = Vue.extend({
template: "<div>Child Component1 : Niklesh : {{name}} <div v-if='loading'>Loading...</div></div>",
props: ['name','loading']
});
var child2 = Vue.extend({
template: "<div>Child Component1 : Rishi : {{name}} <div v-if='loading'>Loading...</div></div>",
props: ['name','loading']
});
var app = new Vue({
el: "#vue-instance",
data: {
name: "...",
loading: true
},
mounted() {
var vm = this;
setTimeout(function() {
vm.name = "Raut";
vm.loading = false;
}, 1000);
},
components: {
child1,
child2
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.1/vue.js"></script>
<div id="vue-instance">
<child1 :name="name" :loading="loading"></child1>
<child2 :name="name" :loading="loading"></child2>
</div>
Use another Vue instance as an Event bus
Code Pen Sample
<div id="app">
<child></child>
</div>
var bus = new Vue()
Vue.component('child', {
data() {
return {
message: 'waiting for event...'
}
},
template: '<div>{{ message }}</div>',
created: function() {
bus.$on('foo', data => {
this.message = data;
})
}
})
var app = new Vue({
el: '#app',
created: function() {
setTimeout(()=> {
bus.$emit('foo', 'bar')
}, 1000)
}
})

Categories