Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.
Vue Router is a URL router that maps URLs to components.
In this article, we’ll look at how to fetch data while navigating between routes.
Data Fetching
We sometimes have to fetch data from the server when a route is activated. For example, we may want to fetch user data when a route is loaded.
With Vue Router, we can fetch data after navigation or before.
When we fetch data after navigation, we perform the navigation first, then fetch data in the component’s lifecycle hook and display a loading state while data is being fetched.
When we fetch data before navigation, we fetch the data as the route enters a navigation guard, then perform the navigation after data is fetched.
Fetching After Navigation
We can fetch data after navigation by navigation and render the component first, the fetch the data in the component’s created
hook.
This gives us the opportunity to display a loading state while the data is being fetched and we can also handle loading differently in each view.
For example, we load data after navigation as follows:
src/index.js
:
const User = {
data() {
return {
user: {}
};
},
created() {
this.fetchData();
},
watch: {
$route: "fetchData"
},
methods: {
async fetchData() {
this.user = await Promise.resolve({ id: this.$route.params.id });
}
},
template: `
<div>user: {{user.id}}</div>
`
};
const routes = [
{
path: "/:id",
component: User
}
];
const router = new VueRouter({
routes
});
new Vue({
el: "#app",
router
});
index.html
:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="UTF-8" />
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-view></router-view>
</div>
<script src="src/index.js"></script>
</body>
</html>
In the code above, we have the User
component that has the fetchData
to fetch data by resolving a promise and setting the result to this.user
. This is done each time the $route
object changes.
So when we go to /#/1
, we get user: 1
, and when we go to /#/2
, we get user: 2
.
We can add a loading
state to fetchData
as follows:
const User = {
data() {
return {
user: {},
loading: true
};
},
created() {
this.fetchData();
},
watch: {
$route: "fetchData"
},
methods: {
async fetchData() {
this.loading = true;
this.user = await Promise.resolve({ id: this.$route.params.id });
this.loading = false;
}
},
template: `
<div>
<div v-if='!loading'>user: {{user.id}}</div>
<div v-if='loading'>Loading</div>
</div>
`
};
const routes = [
{
path: "/:id",
component: User
}
];
const router = new VueRouter({
routes
});
new Vue({
el: "#app",
router
});
Then when the data loads in fetchData
, we have this.loading
set to true
. Otherwise, it’s set to false
.
Fetching Before Navigation
we can fetch data before navigating to a new route by using the beforeRouteEnter
hook.
Then in the next
hook, we can pass in a callback that calls a method in our component to set the data.
For example, we can write the following code to do that:
src/index.js
:
const User = {
data() {
return {
user: {},
loading: true
};
},
async beforeRouteEnter(to, from, next) {
const user = await Promise.resolve({ id: to.params.id });
next(vm => vm.setUser(user));
},
async beforeRouteUpdate(to, from, next) {
const user = await Promise.resolve({ id: to.params.id });
this.setUser(user);
next();
},
methods: {
setUser(user) {
this.loading = true;
this.user = user;
this.loading = false;
}
},
template: `
<div>
<div v-if='!loading'>user: {{user.id}}</div>
<div v-if='loading'>Loading</div>
</div>
`
};
const routes = [
{
path: "/:id",
component: User
}
];
const router = new VueRouter({
routes
});
new Vue({
el: "#app",
router
});
index.html
:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="UTF-8" />
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
<router-view></router-view>
</div>
<script src="src/index.js"></script>
</body>
</html>
In the code above, the beforeRouteEnter
hook loads the data before the component is fully loaded, so we have to call next(vm => vm.setUser(user));
to set data.user
. vm
is the component instance, so the setUser
method is a property of vm
.
When the route parameter updates, beforeRouteUpdate
will be called. We can get the data and call this.setUser
directly since the component is already loaded.
Then when we go to /#/1
, we get user: 1
, and when we go to /#/2
, we get user: 2
.
It’s also a good idea to display a progress bar or some kind of indicator while data is loading.
Conclusion
We can load data before or after route navigation is done. To do it after, we can load the data in the created
hook and watch the $route
object for updates.
If we want to load data before route navigation is done, we can load the data in the beforeRouteEnter
hook and the call next
with a callback that has the component instance as the parameter to call its methods and set the data.
Then when the parameter or query string updates, we can run code in the beforeRouteUpdate
hook to load data there.