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 defining route and in-component guards and look at the full navigation resolution flow.
Per-Route Guard
We can also define navigation guards per route. For example, we can define it as follows:
src/index.js :
const Login = { template: "<div>login</div>" };
const Profile = { template: "<div>profile</div>" };
const routes = [
{
path: "/",
component: Login
},
{
path: "/profile",
component: Profile,
beforeEnter: (to, from, next) => {
if (!localStorage.getItem("token")) {
next("/");
} else {
next();
}
}
}
];
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>
The beforeEnter hook for /profile checks if localStorage.token is defined. If it is, then we let them through. Otherwise, we go back to / .
Therefore, if localStorage.token is defined, then we can go to /#/profile and see profile displayed. Otherwise, we get redirected back to /#/ and see login.
The signature is the same as the global beforeEach callback.
In-Component Guards
We can define navigation guards inside a component, the hooks are the following:
beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave
For example, we can use them as follows:
beforeRouteEnter
If we want to check if a token is present before proceeding to a route, we can write some like the following code:
src/index.js :
const Login = { template: "<div>login</div>" };
const Profile = {
data() {
return { user: "foo" };
},
template: "<div>profile</div>",
beforeRouteEnter(to, from, next) {
if (!localStorage.getItem("token")) {
return next("/");
}
next(vm => {
alert(\`You're logged in as ${vm.user}`);
});
}
};
const routes = [
{
path: "/",
component: Login
},
{
path: "/profile",
component: Profile
}
];
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 beforeRouteEnter hook to check if the token is present in local storage. If it is then, we proceed. Otherwise, we go back to / .
If we proceed, then next is called with a callback with vm , which is the component object as the parameter. Therefore, we can get the user property from vm in the next callback.
In the end, we should see ‘You’re logged in as foo’ alert box displayed if a localStorage.token is defined.
Only beforeRouteEnter accepts a callback for next since the component isn’t available before navigation is done.
beforeRouteUpdate
We can use beforeRouteUpdate as follows:
src/index.js :
const Profile = {
data() {
return {
id: undefined
};
},
template: "<div>id: {{id}}</div>",
beforeRouteUpdate(to, from, next) {
this.id = to.params.id;
next();
}
};
const routes = [
{
path: "/profile/:id",
component: Profile
}
];
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>
Then when we go /#/profile/1 , then /#/profile/2 , we’ll see id: 2 displayed.
beforeRouteUpdate watches for route updates, so it does nothing when the route first loads.
beforeRouteLeave
We can use beforeRouteLeave to run code before a user navigates away from a route.
For example, we can use it as follows:
src/index.js :
const Profile = {
template: "<div>profile</div>",
beforeRouteLeave(to, from, next) {
const answer = window.confirm("Are you sure you want to leave?");
if (answer) {
next();
} else {
next(false);
}
}
};
const routes = [
{
path: "/profile",
component: Profile
}
];
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 beforeRouteLeave hook that runs before a user tries to leave a route.
We have:
const answer = window.confirm("Are you sure you want to leave?");
to ask the user if he wants to leave. If he clicks OK, then the navigation will continue by calling next() . Otherwise, next(false) is called and navigation is canceled.
Full Navigation Resolution Flow
The full flow for navigation resolution is as follows:
- navigation triggered
beforeRouteLeavecalled- global
beforeEachguard called beforeRouterUpdatein component calledbeforeEnterin route configs called- resolve async route components
beforeEnterin activated components called- global
beforeResolveguards called - navigation confirmed
- global
afterEachhooks called - DOM updates triggered
- callbacks passed to
nextinbeforeRouteEntercalled
Conclusion
We can have route guards in individual routes and components. They have their own hooks but they can serve the same purpose per route or component.
The only exception is the beforeRouteEnter which takes a callback in the next function.
Global navigation guards are generally called before local ones.