Vue Router Router and Component Guards and Navigation Resolution Flow

John Au-Yeung - Jan 25 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

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  
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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  
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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  
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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  
});
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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?");
Enter fullscreen mode Exit fullscreen mode

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:

  1. navigation triggered
  2. beforeRouteLeave called
  3. global beforeEach guard called
  4. beforeRouterUpdate in component called
  5. beforeEnter in route configs called
  6. resolve async route components
  7. beforeEnter in activated components called
  8. global beforeResolve guards called
  9. navigation confirmed
  10. global afterEach hooks called
  11. DOM updates triggered
  12. callbacks passed to next in beforeRouteEnter 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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .