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.

Categories
JavaScript Vue

Add a Timeline Chart to a Vue App

We can add a timeline chart to a Vue app with the vue-cute-timeline package.

vue-cute-timeline is a timeline component that we can use with Vue apps.

It displays a vertical timeline on our screen.

To install it, we run:

npm i vue-cute-timeline

Then we can use it by writing:

<template>
  <div id="app">
    <timeline>
      <timeline-title>title</timeline-title>
      <timeline-item bg-color="#9dd8e0">foo</timeline-item>
      <timeline-item :hollow="true">bar</timeline-item>
    </timeline>
  </div>
</template>
 
<script>
import { Timeline, TimelineItem, TimelineTitle } from "vue-cute-timeline";
export default {
  components: {
    Timeline,
    TimelineItem,
    TimelineTitle
  }
};
</script>

We just import the timeline components.

Then we get a vertical timeline with the content between the tags.

bg-color has a background color.

hollow makes the dot hollow.

vue-cute-timeline lets us add a timeline to our app easily.

Categories
GraphQL JavaScript

An Introduction to GraphQL

GraphQL is a query language for our API and a server-side runtime for running queries by using a type system for our data.

In this article, we’ll look at how to make basic queries to a GraphQL API.

Defining the API

We define an API by defining the types and fields for those types and provide functions for each field on each type.

For example, if we have the following type:

type Query {  
  person: Person  
}

Then we have to create a function for the corresponding type to return the data:

function Query_person(request) {  
  return request.person;  
}

Making Queries

Once we have a GraphQL service running, we can send GraphQL queries to validate and execute on the server.

For example, we can make a query as follows:

{  
  person {  
    firstName  
  }  
}

Then we may get JSON like the following:

{  
  "person": {  
    "firstName": "Joe"  
  }  
}

Queries and Mutations

Queries are for getting data from the GraphQL server and mutations are used for manipulating data stored on the server.

For example, the following is a query to get a person’s name:

{  
  person {  
    name  
  }  
}

Then we may get the following JSON from the server:

{  
  "data": {  
    "person": {  
      "name": "Joe"  
    }  
  }  
}

The field name returns a String type.

We can change the query as we wish if we want to get more data. For example, if we write the following query:

{  
  person {  
    name  
    friends {  
      name  
    }  
  }  
}

Then we may get something like the following as a response:

{  
  "data": {  
    "person": {  
      "name": "Joe",  
      "friends": [  
        {  
          "name": "Jane"  
        },  
        {  
          "name": "John"  
        }  
      ]  
    }  
  }  
}

The example above has friends being an array. They look the same from the query perspectively, but the server knows what to return based on the type specified.

Arguments

We can pass in arguments to queries and mutations. We can do a lot more with queries if we pass in arguments to it.

For example, we can pass in an argument as follows:

{  
  person(id: "1000") {  
    name      
  }  
}

Then we get something like:

{  
  "data": {  
    "person": {  
      "name": "Luke"  
    }  
  }  
}

from the server.

With GraphQL, we can pass in arguments to nested objects. For example, we can write:

{  
  person(id: "1000") {  
    name  
    height(unit: METER)  
  }  
}

Then we may get the following response:

{  
  "data": {  
    "person": {  
      "name": "Luke",  
      "height": 1.9  
    }  
  }  
}

In the example, the height field has a unit which is an enum type that represents a finite set of values.

unit may either be METER or FOOT.

Fragments

We can define fragments to let us reuse complex queries.

For example, we can define a fragment and use it as follows:

{  
  leftComparison: person(episode: FOO) {  
    ...comparisonFields  
  }  
  rightComparison: person(episode: BAR) {  
    ...comparisonFields  
  }  
}  
​  
fragment comparisonFields on Character {  
  name  
  appearsIn  
  friends {  
    name  
  }  
}

In the code above, we defined the comparisonFields fragment which has the list of fields we want to include in each query.

Then we have the leftComparison and rightComparison queries which include the fields of the comparisonFields fragment by using the ... operator.

Then we get something like:

{  
  "data": {  
    "leftComparison": {  
      "name": "Luke",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Jane"  
        },  
        {  
          "name": "John"  
        }  
      ]  
    },  
    "rightComparison": {  
      "name": "Mary",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Mary"  
        },  
        {  
          "name": "Alex"  
        }  
      ]  
    }  
  }  
}

Using variables inside fragments

We can pass in variables into fragments as follows:

query PersonComparison($first: Int = 3){  
  leftComparison: person(episode: FOO) {  
    ...comparisonFields  
  }  
  rightComparison: person(episode: BAR) {  
    ...comparisonFields  
  }  
}  
​  
fragment comparisonFields on Character {  
  name  
  appearsIn  
  friends(first: $first) {  
    name  
  }  
}

Then we may get something like:

{  
  "data": {  
    "leftComparison": {  
      "name": "Luke",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Jane"  
        },  
        {  
          "name": "John"  
        }  
      ]  
    },  
    "rightComparison": {  
      "name": "Mary",  
      "appearsIn": [  
        "FOO",  
        "BAR"  
      ],  
      "friends": [  
        {  
          "name": "Mary"  
        },  
        {  
          "name": "Alex"  
        }  
      ]  
    }  
  }  
}

as a response.

The operation type may either be a query, mutation, or subscription and describes what operator we’re intending to do. It’s required unless we’re using the query shorthand syntax. In that case, we can’t supply a name or variable definition for our operation.

The operation name is a meaningful and explicit name for our operation. It’s required in multi-operation documents. But its use is encouraged because it’s helpful for debugging and server-side logging.

It’s easy to identify the operation with a name.

Conclusion

GraphQL is a query language that lets us send requests to a server in a clear way. It works by sending nested objects with operation type and name along with any variables to the server.

Then the server will return us the response that we’re looking for.

Operation types include queries for getting data and mutations for making changes to data on the server.