My first Vue project and I want to run a loading effect on every router call.
I made a Loading component:
<template>
<b-loading :is-full-page="isFullPage" :active.sync="isLoading" :can-cancel="true"></b-loading>
</template>
<script>
export default {
data() {
return {
isLoading: false,
isFullPage: true
}
},
methods: {
openLoading() {
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 10 * 1000)
}
}
}
</script>
And I wanted to place inside the router like this:
router.beforeEach((to, from, next) => {
if (to.name) {
Loading.openLoading()
}
next()
}
But I got this error:
TypeError: "_components_includes_Loading__WEBPACK_IMPORTED_MODULE_9__.default.openLoading is not a function"
What should I do?
Vuex is a good point. But for simplicity you can watch $route in your component, and show your loader when the $route changed, like this:
...
watch: {
'$route'() {
this.openLoading()
},
},
...
I think it's fast and short solution.
I don't think you can access a component method inside a navigation guard (beforeEach) i would suggest using Vuex which is a vue plugin for data management and then making isLoading a global variable so before each route navigation you would do the same ... here is how you can do it :
Of course you need to install Vuex first with npm i vuex ... after that :
on your main file where you are initializing your Vue instance :
import Vue from 'vue'
import Vuex from 'vue'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
isLoading: false,
},
mutations: {
openLoading(state) {
state.isLoading = true
setTimeout(() => {
state.isLoading = false
}, 10000)
},
},
})
// if your router is on a separated file just export the store and import it there
const router = new VueRouter({
routes: [
{
// ...
},
],
})
router.beforeEach((to, from, next) => {
if (to.name) {
store.commit('openLoading')
}
next()
})
new Vue({
/// ....
router,
store,
})
In your component:
<b-loading :is-full-page="isFullPage" :active.sync="$store.state.isLoading" :can-cancel="true"></b-loading>
I am loading data in root Vue instance using ajax and trying to pass it to child components. And I am getting undefined data. Of course, It is happening becasue of Async nature of ajax call so my question is how to do it without render blocking the child components using v-if. What is best practice to do it.
Here is my demo app -
https://codepen.io/xblack/pen/jXQeWv?editors=1010
Vue.component('child-one',{
template:'#child-one',
props:['mydataOne']
});
Vue.component('child-two',{
template:'#child-two',
props:['mydataTwo']
});
let app = new Vue({
el:'#app',
data:{
welcome:'Hello World',
mydata:null
},
methods:{
getdataApi:function(){
var self = this;
$.getJSON( "https://jsonplaceholder.typicode.com/users", function( data ) {
self.mydata = data;
});
}
},
created:function(){
this.getdataApi();
},
mounted:function(){
this.getdataApi();
console.log('api data->',this.mydata);
}
});
Here is the main reason:
https://stackoverflow.com/a/54163297/523630
Your mydataOne and mydataTwo properties should be mydata-one and mydata-two in component's html snippet, like so:
<div :mydata-one="mydata"></div>
Here is a working snipet of your code:
https://codepen.io/anon/pen/Jwewde?editors=1010
Vue.component('child-one',{
template:'#child-one',
props:['one']
});
Vue.component('child-two',{
template:'#child-two',
props:['two']
});
let app = new Vue({
el:'#app',
data:{
welcome:'Hello World',
mydata:null
},
methods:{
getdataApi(){
$.getJSON( "https://jsonplaceholder.typicode.com/users", (data) => {
this.mydata = data;
});
}
},
mounted:function(){
this.getdataApi();
}
});
Here you can read about loading JSON data in Vue using axios:
https://v2.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html
Got this working with a single page. It's when I introduce vue-router that nothing will render.
In main.js, if I return app.js with sample text and few edits; it renders fine with no issues. Seems it has to do with how I'm instantiating vue-router. Also, no console errors in my environment.
main.js
define(function(require) {
'use strict';
var Vue = require('vue');
var VueRouter = require('vue-router');
var App = require('app');
var Foo = { template: '<div>oijsdfoijsdoifjdsf</div>' }
var Bar = { template: '<div>bar</div>' }
Vue.use(VueRouter);
var routes = [
{ path: '/aaa', component: Foo },
{ path: '/bbb', component: Bar }
]
var router = new VueRouter({
routes: routes
});
return new Vue({
el: '#vue',
router: router,
render: function(h) {
h(App);
}
});
});
app.js
define(function(require) {
'use strict';
var Vue = require('vue');
return new Vue({
template: '<div id="vue"><router-view></router-view></div>'
});
});
From my experience, you can change your router configuration to:
const router = new VueRouter({
base: __dirname, // or '/'
routes: routes
})
You haven't defined a root route, e.g. /. So you wouldn't see any component render at /. Can you navigate to /aaa and see something? Also, shouldn't those route components be passed to Vue.component?
I have a Vue.js app. This app shows views using the vue-router. When the app starts, I need to retrieve a key from the server. I then want to pass that key to the child views. To demonstrate the problem I've setup this Fiddle. The code related to this question like this:
var Page1 = { template: '<div><h3>Page 1</h3><p>Number: {{ number }}</p></div>', props: [ 'number' ] };
var Page2 = { template: '<div><h3>Page 2</h3><p>Number: {{ number }}</p></div>', props: [ 'number' ] };
var routes = [
{ path: '/page-1', component: Page1 },
{ path: '/page-2', component: Page2 }
];
var router = new VueRouter({
routes
});
var app = new Vue({
router,
el: '#app',
data: {
num: 0
},
created: function() {
var self = this;
setTimeout(function() {
self.num = 1;
}, 100);
}
});
When I run this app, I'm not sure how to "pass" the app.num to the child views (Page1 and Page2) when app.num gets set. I tried using the approach mentioned in the "passing props to route components" docs. However, that approach doesn't seem to work for data that is loaded after creation. What am I missing?
You can bind values from the parent component to the child via the router-view like this:
<router-view :number="num"></router-view>
Is there a way to re-render a component on route change? I'm using Vue Router 2.3.0, and I'm using the same component in multiple routes. It works fine the first time or if I navigate to a route that doesn't use the component and then go to one that does. I'm passing what's different in props like so
{
name: 'MainMap',
path: '/',
props: {
dataFile: 'all_resv.csv',
mapFile: 'contig_us.geo.json',
mapType: 'us'
},
folder: true,
component: Map
},
{
name: 'Arizona',
path: '/arizona',
props: {
dataFile: 'az.csv',
mapFile: 'az.counties.json',
mapType: 'state'
},
folder: true,
component: Map
}
Then I'm using the props to load a new map and new data, but the map stays the same as when it first loaded. I'm not sure what's going on.
The component looks like this:
data() {
return {
loading: true,
load: ''
}
},
props: ['dataFile', 'mapFile', 'mapType'],
watch: {
load: function() {
this.mounted();
}
},
mounted() {
let _this = this;
let svg = d3.select(this.$el);
d3.queue()
.defer(d3.json, `static/data/maps/${this.mapFile}`)
.defer(d3.csv, `static/data/stations/${this.dataFile}`)
.await(function(error, map, stations) {
// Build Map here
});
}
You may want to add a :key attribute to <router-view> like so:
<router-view :key="$route.fullPath"></router-view>
This way, Vue Router will reload the component once the path changes. Without the key, it won’t even notice that something has changed because the same component is being used (in your case, the Map component).
UPDATE --- 3 July, 2019
I found this thing on vue-router documentation, it's called In Component Guards. By the description of it, it really suits your needs (and mine actually). So the codes should be something like this.
export default () {
beforeRouteUpdate (to, from, next) {
// called when the route that renders this component has changed,
// but this component is reused in the new route.
// For example, for a route with dynamic params `/foo/:id`, when we
// navigate between `/foo/1` and `/foo/2`, the same `Foo` component instance
// will be reused, and this hook will be called when that happens.
// has access to `this` component instance.
const id = to.params.id
this.AJAXRequest(id)
next()
},
}
As you can see, I just add a next() function. Hope this helps you! Good luck!
Below is my older answer.
Only saved for the purpose of "progress"
My solution to this problem was to watch the $route property.
Which will ended up you getting two values, that is to and from.
watch: {
'$route'(to, from) {
const id = to.params.id
this.AJAXRequest(id)
}
},
The alternate solution to this question handles this situation in more cases.
First, you shouldn't really call mounted() yourself. Abstract the things you are doing in mounted into a method that you can call from mounted. Second, Vue will try to re-use components when it can, so your main issue is likely that mounted is only ever fired once. Instead, you might try using the updated or beforeUpdate lifecycle event.
const Map = {
data() {
return {
loading: true,
load: ''
}
},
props: ['dataFile', 'mapFile', 'mapType'],
methods:{
drawMap(){
console.log("do a bunch a d3 stuff")
}
},
updated(){
console.log('updated')
this.drawMap()
},
mounted() {
console.log('mounted')
this.drawMap()
}
}
Here's a little example, not drawing the d3 stuff, but showing how mounted and updated are fired when you swap routes. Pop open the console, and you will see mounted is only ever fired once.
you can use just this code:
watch: {
$route(to, from) {
// react to route changes...
}
}
Yes, I had the same problem and solved by following way;
ProductDetails.vue
data() {
return {
...
productId: this.$route.params.productId,
...
};
},
methods: {
...mapActions("products", ["fetchProduct"]),
...
},
created() {
this.fetchProduct(this.productId);
...
}
The fetchProduct function comes from Vuex store. When an another product is clicked, the route param is changed by productId but component is not re-rendered because created life cycle hook executes at initialization stage.
When I added just key on router-view on parent component app.vue file
app.vue
<router-view :key="this.$route.path"></router-view>
Now it works well for me. Hopefully this will help Vue developers!
I was having the same issue, but slightly different. I just added a watch on the prop and then re-initiated the fetch method on the prop change.
import { ref, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import Page from './content/Page.vue';
import Post from './content/Post.vue';
const props = defineProps({ pageSlug: String });
const pageData = ref(false);
const pageBodyClass = ref('');
function getPostContent() {
let postRestEndPoint = '/wp-json/vuepress/v1/post/' + props.pageSlug;
fetch(postRestEndPoint, { method: 'GET', credentials: 'same-origin' })
.then(res => res.json())
.then(res => {
pageData.value = res;
})
.catch(err => console.log(err));
}
getPostContent();
watch(props, (curVal, oldVal) => {
getPostContent();
});
watch(pageData, (newVal, oldVal) => {
if (newVal.hasOwnProperty('data') === true && newVal.data.status === 404) {
pageData.value = false;
window.location.href = "/404";
}
});
router - index.js
{
path: "/:pageSlug",
name: "Page",
component: Page,
props: true,
},
{
path: "/product/:productSlug",
name: "Product",
component: Product,
},
{
path: "/404",
name: "404",
component: Error404,
}