Categories
JavaScript Vue

Using Vue Router’s History Mode

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 use Vue Router’s history mode.

HTML5 History Mode

The default mode for Vue Router is hash mode. It uses a URL hash to simulate a full URL so that the page won’t be reloaded when the URL changes.

We can set Vue Router to history mode to get rid of the hash. It uses history.pushState API to let us navigate URLs without a page reload.

For example, we can enable history mode as follows:

src/index.js :

const Foo = { template: "<div>foo</div>" };  
const Bar = { template: "<div>bar</div>" };  
const routes = [  
  {  
    path: "/foo",  
    component: Foo  
  },  
  {  
    path: "/bar",  
    component: Bar  
  }  
];

const router = new VueRouter({  
  mode: "history",  
  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 to /foo and /bar , we’ll see foo and bar displayed respectively. Now we don’t need to add a hash sign in front of the URL anymore.

Server Configuration

The issue with history mode is that we have to configure our server to display our app whenever the user navigates to the folder in our server.

This is because if we didn’t configure our server, we’ll get a 404 error if we go directly to the URL from the browser.

To fix this, we need to redirect to index.html if it doesn’t match any assets uploaded to the server that we want to load.

We can use the following configure in Apache:

<IfModule mod_rewrite.c>  
  RewriteEngine On  
  RewriteBase /  
  RewriteRule ^index.html$ - [L]  
  RewriteCond %{REQUEST_FILENAME} !-f  
  RewriteCond %{REQUEST_FILENAME} !-d  
  RewriteRule . /index.html [L]  
</IfModule>

This configuration assumes that the code is in the root folder. If it’s in a subfolder, we can change RewriteBase / with RewriteBase /subfolder-name/ .

For Nginx, it’s simpler:

location / {  
  try_files $uri $uri/ /index.html;  
}

Again, we can replace / with the subfolder if the code is in a subfolder.

We should add a 404 route to our app since how 404 errors won’t be displayed.

We can add one as follows:

src/index.js :

const Foo = { template: "<div>foo</div>" };  
const Bar = { template: "<div>bar</div>" };  
const NotFound = { template: "<div>not found</div>" };  
const routes = [  
  {  
    path: "/foo",  
    component: Foo  
  },  
  {  
    path: "/bar",  
    component: Bar  
  },  
  { path: "*", component: NotFound }  
];

const router = new VueRouter({  
  mode: "history",  
  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>

With Node.js, the easiest way to serve a Vue app with history mode set by using Express and the connect-history-api-fallback middleware as follows:

server.js :

const express = require('express');  
const history = require('connect-history-api-fallback');

const app = express();  
app.use(history());  
app.use(express.static('src'));

app.get('/', (req, res) => {  
  res.sendFile('src/index.html');  
});

app.listen(3000, () => console.log('server started'));

Then the Vue app files can be stored in the src folder.

In the code above, the code:

{ path: "\*", component: NotFound }

is the catch-all route for anything other than foo and bar and maps to the NotFound component.

Conclusion

We can enable the Vue Router’s history mode to eliminate the hash from our app’s URLs.

However, we have to redirect our app to index.html since we don’t want users to see errors when they go the URLs by entering it or refreshing the page.

We can do this with any web server. Then we have to add a catch-all route to display something when it doesn’t match any routes.

Categories
JavaScript Vue

Introduction to Vue Router Navigation Guards

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 add navigation guards to do checks before navigation is done.

Global Before Guards

Router parameter or query changes won’t trigger enter or leave navigation guards.

We can watch the $route object or use the beforeRouteUpdate in-component guard to check those changes.

For other route changes, we can use navigation guards to run checks before navigating to a route.

The beforeEach method takes a callback as follows:

const router = new VueRouter({ ... })  
  
router.beforeEach((to, from, next) => {  
  // ...  
})

The 3 parameters are the following:

  • to — the target route object being navigated to
  • from — the current route that’s being navigated from
  • next — a function that’s called to resolve the hook

The next function takes one argument. It can take on several possible values.

If nothing is passed in, then we can navigate to the next route. If we pass in false , then the current navigation is aborted.

If we pass in '/' or { path: '/' } , then we redirect to the / route. The current navigation is aborted and a new one is started. We can also set the replace and name properties like as we do with router.push or router.replace .

We can also pass in an Error instance since Vue 2.4.0, then the navigation will be aborted and the error will be passed to callbacks registered via router.onError() .

We should only call next once in our guard.

For example, we can create a navigation guard to check navigation 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  
  }  
];

const router = new VueRouter({  
  routes  
});

router.beforeEach((to, from, next) => {  
  if (to.path === "/") {  
    return next();  
  } if (!localStorage.getItem("token")) {  
    next("/");  
  } else {  
    next();  
  }  
});

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, our navigation guard first checks if we’re going to / , if we are, then we let them navigate to it.

Otherwise, we check 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.

Global Resolve Guards

Vue Router also has router.beforeResolve , which is similar to router.beforeEach but it’s called right before navigation is confirmed, and after all in-component guards and async route components are resolved.

Global After Hooks

We can also run code after navigation is done with the afterEach hook.

For example, we can use it as follows:

src/index.js :

const Foo = { template: "<div>foo</div>" };  
const Bar = { template: "<div>bar</div>" };  
const routes = [  
  {  
    path: "/",  
    component: Foo  
  },  
  {  
    path: "/bar",  
    component: Bar  
  }  
];

const router = new VueRouter({  
  routes  
});

router.afterEach((to, from) => {  
  alert(`Navigated to ${to.path}`);  
});  
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](https://unpkg.com/vue/dist/vue.js)"></script>  
    <script src="[https://unpkg.com/vue-router/dist/vue-router.js](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 we get an alert box that shows where we navigate to when we navigate.

Note that it doesn’t have the next function in the callback signature because it can’t go to the next route.

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.

Conclusion

We run code before and after navigation with navigation guards.

Before hooks are handy for doing checks before navigation is done. For example, it’s handy for authentication checks.

After hooks are handy for running code after navigation is done.

We can define hooks per route or globally.

Categories
JavaScript Vue

Adding Meta Fields and Transitions to Vue Router Routes

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 add meta fields and transition effects to routes.

Adding Meta Fields to Routes

We can add meta fields to routes so that they can be checked in navigation guards and components.

For example, we can add a meta field indicating which fields need authentication and then check if an auth token is stored in local storage 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,  
    meta: { requireAuth: true }  
  }  
];

const router = new VueRouter({  
  routes  
});

router.beforeEach((to, from, next) => {  
  if (to.meta.requireAuth) {  
    if (!localStorage.getItem("token")) {  
      next("/");  
    } else {  
      next();  
    }  
  } else {  
    next();  
  }  
});

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 code above has a global beforeEach guard to check if meta field with requiredAuth set to true .

Then if it is, we’ll check if localStorage.token exists, and then complete navigation if it’s present.

Otherwise, we cancel navigation of the current route and navigate to / .

In all other cases, we continue with the navigation.

Accessing Route Meta Field within Components

We can access the meta field in components using the $route.meta field.

For example, we can write the following to get the id meta field displayed in our template:

src/index.js :

const User = {  
  template: "<div>user {{$route.meta.id}}</div>"  
};

const routes = [  
  {  
    path: "/",  
    component: User,  
    meta: { id: 1 }  
  }  
];

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 we should see user 1 displayed since we have $route.meta.id in our template.

Adding Transition Effect

We can apply transition effects to router-view the same way as any other component.

For example, we can apply per-route transition by writing the following:

src/index.js :

const Foo = {  
  template: `  
    <transition name="slide">  
      <div>foo</div>  
    </transition>  
  `  
};

const Bar = {  
  template: `  
    <transition name="fade">  
      <div>bar</div>  
    </transition>  
  `  
};

const routes = [  
  {  
    path: "/foo",  
    component: Foo  
  },  
  {  
    path: "/bar",  
    component: Bar  
  }  
];

const router = new VueRouter({  
  routes  
});

new Vue({  
  el: "#app",  
  router  
});

src/styles.css :

.fade-enter-active,  
.slide-enter-active {  
  transition: all 0.3s ease;  
}  
.fade-leave-active,  
.slide-leave-active {  
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);  
}  
.fade-enter,  
.fade-leave-to,  
.slide-enter,  
.slide-leave-to {  
  transform: translateX(10px);  
  opacity: 0;  
}

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>  
    <link href="src/styles.css" type="text/css" />  
  </head>  
  <body>  
    <div id="app">  
      <router-link to="foo">Foo</router-link>  
      <router-link to="bar">Bar</router-link>  
      <router-view></router-view>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then we see that there’s fade effect as we transition from one route to another by clicking the links.

We can then use different names for each route.

Route-Based Dynamic Transition

We can also do route based dynamic transitions by combining a watcher for $route and dynamically setting the transition name as follows:

src/index.js :

const Foo = {  
  template: ` 
    <transition name="slide">  
      <div>foo</div>  
    </transition>  
  `  
};

const Bar = {  
  template: `  
    <transition name="fade">  
      <div>bar</div>  
    </transition>  
  `  
};

const routes = [  
  {  
    path: "/foo",  
    component: Foo  
  },  
  {  
    path: "/bar",  
    component: Bar  
  }  
];

const router = new VueRouter({  
  routes  
});

new Vue({  
  el: "#app",  
  router,  
  data: {  
    transitionName: ""  
  },  
  watch: {  
    $route(to, from) {  
      this.transitionName = to.path === "/bar" ? "slide-right" : "slide-left";  
    }  
  }  
});

src/styles.css :

.slide-left-enter-active,  
.slide-right-enter-active {  
  transition: all 0.3s ease;  
}  

.slide-left-leave-active,  
.slide-right-leave-active {  
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);  
}  
.slide-right-enter,  
.slide-right-leave-to {  
  transform: translateX(10px);  
  opacity: 0;  
}

.slide-left-enter,  
.slide-left-leave-to {  
  transform: translateX(-10px);  
  opacity: 0;  
}

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>  
    <link href="src/styles.css" type="text/css" />  
  </head>  
  <body>  
    <div id="app">  
      <router-link to="foo">Foo</router-link>  
      <router-link to="bar">Bar</router-link>  
      <transition :name="transitionName">  
        <router-view></router-view>  
      </transition>  
    </div>  
    <script src="src/index.js"></script>  
  </body>  
</html>

Then when we click on Foo , bar fades away by sliding to the left. And when we click Bar , foo fades away by sliding to the right.

Conclusion

We can add meta fields to store extra data about a route. We can access it via navigation guards’ parameters or the $route object in components.

Like any other component, we can apply transition effects to router-view . All transition API work with router-view .

Categories
JavaScript Vue

Vue Router Router and Component Guards and Navigation Resolution Flow

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:

  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.

Categories
JavaScript Vue

Fetching Data in Navigation with Vue Router

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.