Categories
Vue 3

Vue Router 4–Scroll Behavior

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Scroll Behavior

We can change the scroll behavior with the Vue Router.

To do that, we add the scrollBehavior method to the object that we pass into the createRouter method.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <router-view></router-view>
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
    </div>
    <script>
      const Foo = {
        template: `<div>
          <p v-for='n in 100'>{{n}}</p>
        </div>`
      };
      const Bar = {
        template: "<div>bar</div>"
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes,
        scrollBehavior(to, from, savedPosition) {
          return { left: 0, top: 500 };
        }
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We return an object with the left and top properties.

left is the x coordinate and top is the y coordinate we want to scroll to when the route changes.

to has the route object of the route we’re moving to.

And from has the route object we’re moving from.

Now when we click on the router links, we move to somewhere near the top of the page.

savedPosition has the position that we scrolled in the previous route.

We can use the savedPosition object as follows:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <router-view></router-view>
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
    </div>
    <script>
      const Foo = {
        template: `<div>
          <p v-for='n in 100'>{{n}}</p>
        </div>`
      };
      const Bar = {
        template: `<div>
          <p v-for='n in 150'>{{n}}</p>
        </div>`
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes,
        scrollBehavior(to, from, savedPosition) {
          if (savedPosition) {
            return savedPosition;
          } else {
            return { left: 0, top: 0 };
          }
        }
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

When the savedPosition object is defined, we return it.

Otherwise, we scroll to the top when we click on the router links.

Now when we scroll to the links, we’ll stay at the bottom of the page.

Conclusion

We can scroll to a given position on the page with Vue Router 4 with the scrollBehavior method.

Categories
Vue 3

Vue Router 4–Per-Route Transition and Fetch Data During Navigation

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Per-Route Transition

We can add transition effects for individual routes.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
    <style>
      .fade-enter-active,
      .fade-leave-active {
        transition: opacity 0.15s ease;
      }
      .fade-enter-from,
      .fade-leave-to {
        opacity: 0;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view v-slot="{ Component, route }">
        <transition :name='route.path.includes("foo") ? `fade`:``'>
          <component :is="Component" />
        </transition>
      </router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>bar</div>"
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We set the name prop dynamically by checking the route object.

The path property has the path of the route.

Therefore, we can check the path to set the value of name to the transition we want.

Data Fetching

We sometimes want to fetch data from the server when a route is activated.

This can be done with various hooks.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>{{bar}}</div>",
        data() {
          return {
            bar: ""
          };
        },
        created() {
          this.fetchData();
        },
        watch: {
          $route: "fetchData"
        },
        methods: {
          async fetchData() {
            this.bar = "bar";
          }
        }
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We watch the $route object and call the fetchData method when $route changes.

Fetching Before Navigation

We can also run methods to fetch data in the before before navigation hooks.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar/1">bar 1</router-link>
        <router-link to="/bar/2">bar 2</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>{{id}}</div>",
        data() {
          return {
            id: ""
          };
        },
        beforeRouteEnter(to, from, next) {
          next((vm) => vm.fetchData(to.params.id));
        },
        beforeRouteUpdate(to, from, next) {
          this.fetchData(to.params.id);
          next();
        },
        methods: {
          async fetchData(id) {
            this.id = id;
          }
        }
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar/:id",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have the beforeRouterEnter and beforeRouteUpdate hooks that runs before the component is rendered and when the route params change respectively.

The fetchData method is run in the callback to get the data.

We have 2 router-link that goes to the /bar route with its own parameter.

We get the id parameter in the navigation guard methods and called fetchData to set its value to the this.id state.

Therefore, we’ll see them in the template.

Conclusion

We have a new way to add per-route transition with Vue Router 4.

Also, we can fetch data in Vue Router hooks with Vue Router 4.

Categories
Vue 3

Vue Router 4–Navigation Guards

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Navigation Guards

We can add navigation guards to guard navigation by redirecting or canceling it.

The guards are only triggered when we navigate to different routes.

URL parameters and query changes won’t trigger enter or leave navigation guards.

Global Before Guards

We can add a global before guard by passing a callback to the router.beforeEach method.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>bar</div>",
        props: ["id"]
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      router.beforeEach((to, from, next) => {
        console.log(to, from);
        next();
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We call the router.beforeEach method with a callback.

The callback takes a to , from and next parameters.

to is a route object being navigated to.

from is a route object being navigated away from.

A route object has the fullPath , metadata, query parameters, URL parameters, and more.

next is a function that must be called to resolve the hook.

We need to call next to show the to route.

The next function can take an argument.

If we pass in false , then the current navigation is aborted.

If the browser URL is changed, then it’ll reset to the one in the from route.

We can also pass in a path string or an object with the path property with the path string.

Either way, we redirect to a different location.

The current navigation is aborted and a new one will be started.

The object can have extra options like the replace and name properties from the route.

Any properties that are accepted by router.push is accepted.

We can also pass in an Error instance if there are any errors.

Then the navigation will be aborted and the error will be passed to callbacks registered with router.onError().

We should make sure that next is called exactly once in the callback.

Otherwise, the navigation may never resolve or errors may result.

So instead of writing:

router.beforeEach((to, from, next) => {
  if (to.name !== 'login' && !isAuthenticated) {
    next({ name: 'login' })
  }
  next()
})

We write:

router.beforeEach((to, from, next) => {
  if (to.name !== 'login' && !isAuthenticated) {
    next({ name: 'login' });
  }
  else {
    next();
  }
})

Global Resolve Guards

We can register a global guard with router.beforteResolve .

It’s similar to router.beforeEach except that it’ll be called right before the navigation is confirmed.

Conclusion

We can add navigation guards to control navigation with Vue Router 4.

Categories
Vue 3

Vue Router 4–Navigation Guard Hooks

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Global After Hooks

We can create global after navigation hooks.

To do that, we call the router.afterEach method with a callback.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>bar</div>"
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      router.afterEach((to, from) => {
        console.log(to, from);
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

to add our after navigation hook.

It’s called after we navigated to the route.

to is a route object being navigated to.

from is a route object being navigated away from.

A route object has the fullPath , metadata, query parameters, URL parameters, and more.

There’s no next function to let us do redirects or throw errors.

Per-Route Guard

We can add navigation guards per route.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>bar</div>"
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar,
          beforeEnter: (to, from, next) => {
            console.log(to, from);
            next();
          }
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have the beforeEnter method in the /bar route object.

The next function lets us resolve the navigation since it’s run before we enter the route.

In-Component Guards

We can add navigation guards inside a component.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>bar</div>",
        beforeEnter(to, from, next) {
          console.log(to, from);
          next();
        },
        beforeRouteUpdate(to, from, next) {
          console.log(to, from);
          next();
        },
        beforeRouteLeave(to, from, next) {
          console.log(to, from);
          next();
        }
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

to add the navigation guards to our Bar component.

The beforeRouteEnter method is called before that renders the component is confirmed.

It doesn’t have access to this component instance since it hasn’t been created yet.

The beforeRouterUpdate method is called when the route that renders the component has changed.

For instance, when a route with dynamic parameters changed, then it’ll be run.

It has access to the this component instance.

beforeRouterLeave is called when the route that renders the component is about to be navigated from.

It also has access to the this component instance.

Conclusion

We can add navigation guard hooks in various locations with Vue Router 4.

Categories
Vue 3

Vue Router 4–In-Component Guards, Route Meta Fields, and Transitions

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

In-Component Guards

The beforeRouteEnter guard is run before the route that renders the component is confirmed

Therefore, it doesn’t have access to this .

However, we can access it within the next callback.

To do that, we write:

beforeRouteEnter(to, from, next) {
  next(vm => {
    // ...
  })
}

The vm has the route component’s instance.

The is the only route that supports passing in a callback for next .

We can use this directly in the beforeRouterUpdate and beforeRouterLeave methods, so passing in a callback to next isn’t supported.

So we can just write:

beforeRouteUpdate (to, from, next) {
  this.name = to.params.name
  next()
}

or:

beforeRouteLeave (to, from, next) {
  const answer = window.confirm('Do you really want to leave?')
  if (answer) {
    next()
  } else {
    next(false)
  }
}

beforeRouteLeave is usually used to prevent the user from accidentally leaving the route.

Navigation Resolution Flow

The steps for navigation are as follows:

  1. Navigation triggered.
  2. Call beforeRouteLeave guards in deactivated components.
  3. Call global beforeEach guards.
  4. Call beforeRouteUpdate guards in reused components.
  5. Call beforeEnter in route configs.
  6. Resolve async route components.
  7. Call beforeRouteEnter in activated components.
  8. Call global beforeResolve guards.
  9. Navigation confirmed.
  10. Call global afterEach hooks.
  11. DOM updates are triggered.
  12. Run callbacks passed to next in beforeRouteEnter guards with instantiated instances.

Route Meta Fields

We can add route meta fields with the meta property.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>bar</div>"
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar,
          meta: { requiresAuth: true }
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      router.beforeEach((to, from, next) => {
        console.log(to.meta.requiresAuth);
        next();
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

to add a meta property to our routes array entry.

We have an object with the requiresAuth property.

Then in the beforeEach callback, we can access it with the to.meta.requiresAuth property.

Transitions

We can add transitions for route changes with Vue Router.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
    <style>
      .fade-enter-active,
      .fade-leave-active {
        transition: opacity 0.5s;
      }
      .fade-enter,
      .fade-leave-to {
        opacity: 0;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view v-slot="{ Component }">
        <transition name="fade">
          <component :is="Component" />
        </transition>
      </router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>bar</div>"
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });

      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

to add transitions to our app.

We add the router-view and access the Component slot prop to get the component that’s displayed through the router-view .

Then we can pass that to the component component inside the router-view .

We set the name prop so that we can set the prefix of the CSS classes we add for the animation.

Between the style tags, we have the styles for various stages of the animation.

The stages of the transitions are listed at https://v3.vuejs.org/guide/transitions-overview.html#class-based-animations-transitions.

Conclusion

We can add in-component guards and route transitions with Vue Router 4.

The way we add route transitions is different from Vue Router 3.