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:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
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
beforeRouteLeave
called- global
beforeEach
guard called beforeRouterUpdate
in component calledbeforeEnter
in route configs called- resolve async route components
beforeEnter
in activated components called- global
beforeResolve
guards called - navigation confirmed
- global
afterEach
hooks called - DOM updates triggered
- callbacks passed to
next
inbeforeRouteEnter
called
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.