I am working on a Vue application that uses a lot of charts. As it is now, I am calling the database in each individual chart component, but this means that all the data is called multiple times, which slows down the page. I would like instead to call the database once and then pass the data to each individual chart through props, but I have run into issues with it.
I have an object with data looks like this:
On my main component I get the data from another component that holds the API (called BIService) On this component I also have all my chart components.
<template>
<b-row cols="1" cols-sm="2" cols-lg="4">
<b-col >
<total-empty-units />
</b-col>
<b-col >
<vacancy-rent-loss />
</b-col>
<b-col >
<churn-rate />
</b-col>
<b-col >
<overdue-payments-chart />
</b-col>
</b-row>
</template>
<script>
import axios from '@/config/axios';
import {biService} from '@/services/bi';
import TotalEmptyUnits from '@/components/chart/empty-units/TotalEmptyUnits';
import OverduePaymentsChart from '@/components/chart/overdue-payments/OverduePaymentsChart';
import ChurnRate from '@/components/chart/churn-rate/ChurnRate';
import VacancyRentLoss from '@/components/chart/vacancy-loss/VacancyRentLoss';
export default {
components: {
TotalEmptyUnits,
OverduePaymentsChart,
ChurnRate,
VacancyRentLoss,
},
data: () => ({
bIGraphStats: null,
}),
methods: {
load() {
biService.getBIGraphStatsForCompany()
.then(result => this.bIGraphStats = result.data)
.catch(error => console.error(error));
},
},
};
</script>
Here is an example of what the chart components look like. As you can see, I am calling the database inside the chart as well.
<template>
<b-card @click="showModal = true">
<div >
<h5 >Churn rate</h5>
<b-button variant="link" >
<i ></i>
</b-button>
</div>
<apexchart
type="radialBar"
height="250"
:chartData="chartData"
:options="options"
:series="series">
</apexchart>
</b-card>
</template>
<script>
import axios from '@/config/axios';
import {biService} from '@/services/bi';
import moment from 'moment';
import VueApexCharts from 'vue-apexcharts'
import Vue from 'vue';
Vue.use(VueApexCharts)
Vue.component('apexchart', VueApexCharts)
export default {
data: () => ({
showModal: false,
chartData: null,
unitGroups: null,
currentMonth: new Date().getMonth(),
series: [],
options: {
chart: {
type: 'radialBar',
offsetY: -20,
sparkline: {
enabled: true
}
},
plotOptions: {
radialBar: {
startAngle: -90,
endAngle: 90,
track: {
background: "#e7e7e7",
strokeWidth: '97%',
margin: 5, // margin is in pixels
dropShadow: {
enabled: true,
top: 2,
left: 0,
color: '#999',
opacity: 1,
blur: 2
}
},
dataLabels: {
formatter: function (val) {
return val;
},
enabled: true,
name: {
show: false
},
value: {
offsetY: -2,
fontSize: '22px',
formatter: function (val) {
return val;
}
}
}
}
},
grid: {
padding: {
top: -10
}
},
fill: {
colors: '#f34860',
},
labels: ['Average Results'],
},
}),
methods: {
currentDateTime() {
return moment().format('MMMM Do YYYY, h:mm:ss a')
}
},
created() {
biService.getBIGraphStatsForCompany()
.then(result => this.series.push(Math.round(result.data.terminatedTenancies)))
.catch(error => console.error(error));
}
}
</script>
So here is what I have tried.
I added a prop to the chart component and called it seriesData, and then I try to pass it into the main component where the database data is fetched. I am using a watcher for this purpose. But nothing is showing up. I am getting the error "Cannot read properties of undefined". on the data I am trying to pass. This is how my new code looks. Can someone guide me to how I might be able to solve this problem?
the main component:
<b-col >
<churn-rate v-if="loaded" :series-data="bIGraphStats" />
</b-col>
The chart component
<template>
<b-card @click="showModal = true">
<div >
<h5 >Churn rate</h5>
<b-button variant="link" >
<i ></i>
</b-button>
</div>
<apexchart
type="radialBar"
height="250"
:chartData="chartData"
:options="options"
:series="series">
</apexchart>
</b-card>
</template>
<script>
import axios from '@/config/axios';
import {biService} from '@/services/bi';
import moment from 'moment';
import VueApexCharts from 'vue-apexcharts'
import Vue from 'vue';
Vue.use(VueApexCharts)
Vue.component('apexchart', VueApexCharts)
export default {
prop: {
seriesData: Object
},
data: () => ({
showModal: false,
chartData: null,
unitGroups: null,
currentMonth: new Date().getMonth(),
series: [],
options: {
chart: {
type: 'radialBar',
offsetY: -20,
sparkline: {
enabled: true
}
},
plotOptions: {
radialBar: {
startAngle: -90,
endAngle: 90,
track: {
background: "#e7e7e7",
strokeWidth: '97%',
margin: 5, // margin is in pixels
dropShadow: {
enabled: true,
top: 2,
left: 0,
color: '#999',
opacity: 1,
blur: 2
}
},
dataLabels: {
formatter: function (val) {
return val;
},
enabled: true,
name: {
show: false
},
value: {
offsetY: -2,
fontSize: '22px',
formatter: function (val) {
return val;
}
}
}
}
},
grid: {
padding: {
top: -10
}
},
fill: {
colors: '#f34860',
},
labels: ['Average Results'],
},
}),
methods: {
currentDateTime() {
return moment().format('MMMM Do YYYY, h:mm:ss a')
}
},
watch: {
seriesData: {
immediate: true,
handler() {
console.log(this.seriesData)
this.series.push(Math.round(this.seriesData.terminatedTenancies))
}
}
}
}
</script>
CodePudding user response:
Props don't wait for asynchronous operations to complete before they're passed down, they are passed down immediately. When the component is first created your prop is null (the initial value). Your child component tries to use null
as if it's already a valid Object which results in your error. You should delay rendering your child component until the initial data is fetched and your prop is no longer null:
<b-col >
<churn-rate v-if="loaded && bIGraphStats" :series-data="bIGraphStats" />
</b-col>
CodePudding user response:
Insted of using watcher and push everytime data changes you can use computed property for series data variable. Since computed provide caching capabilities it's better to go with computed property. More details on computed over watchers vuedocs
In chart component.
computed: {
series () {
if (this.seriesData) {
return this.seriesData
}
return []
}
}