How to Build a Web Application With Vue.js

How to Build a Web Application With Vue.js

In this Vue.js step by step tutorial, you will learn how to build a web app quickly. Learn to build a web app quickly using Vue.js with a comprehensive step by step tutorial. Create a Vue.js Web App. Run and Test the Vue.js Web App. Create an Array of Objects File.

Learn to build a web app quickly using Vue.js with a comprehensive step by step tutorial

In this Vue.js step by step tutorial, you will learn how to build a web app quickly. What we build is not just a simple web page that displays "Hello World". More than that, this web application display a list of data from the local data, displays data details and shows the Google Maps of the current location in data.

This tutorial divided into several steps:

  • Step #1. Preparation
  • Step #2. Create a Vue.js Web App
  • Step #3. Add Vue.js Router
  • Step #4. Create an Array of Objects File
  • Step #5. Show List of Data using Bootstrap-Vue
  • Step #6. Show Data Details using Bootstrap-Vue
  • Step #7. Show Google Maps
  • Step #8. Run and Test the Vue.js Web App

The following tools, framework, and module are required for this tutorial:

  1. Node.js (Recommended version)
  2. Vue.js
  3. Bootstrap-Vue
  4. Terminal (for Mac/Linux) or Node Command Line (for Windows)
  5. IDE or Text Editor (We are using Visual Studio Code)
Step #1. Preparation

We need to prepare the environment to create a Vue.js web app. First, we need a Node.js with the working NPM or Yarn command. We assume that you have already downloaded and installed Node.js. Make sure the Node.js command line is working (on Windows) or runnable in Linux/macOS terminal. To check the Node.js and NPM working type this command in the Terminal.

node -v
v10.15.1
npm -v
6.13.1
yarn -v
1.19.2

Next, we will use a Vue-CLI to create a new Vue.js application. Vue CLI is a full system for rapid Vue.js development with the current latest version is 4.1.1. To install Vue-CLI 4 type this command from the Terminal or Node command line.

sudo npm install -g @vue/cli

or

yarn global add @vue/cli

Next, check the version to make sure that you have the 4.x version of Vue-CLI.

vue --version
@vue/cli 4.1.1
Step #2. Create a Vue.js Web App

To create a Vue.js application using Vue-CLI just type this command in the terminal or cmd.

vue create vue-tutorial

Leave every question as default then wait for package installation finished. Next, go to the newly created folder.

cd vue-tutorial

To make sure that created Vue.js project working, type this command to run the Vue.js application.

npm run serve

or

yarn start

You will see this page when open http://localhost:8080/ in the browser.

To start working with this new Vue.js application, open this Vue.js project with your favorite Text Editor or IDE. To use Visual Studio Code, just type this in the terminal after stopping the running Vue.js application by press CTRL+C.

code .

Now, you can continue to the next step.

Step #3. Add Vue.js Router

If you check the dependencies inside package.json, the Vue-router is not installed yet. For that, type this command to install that module.

npm install --save vue-router

or

yarn add vue-router

Before declaring the vue-router, we need to add the required components for this tutorial.

touch src/components/List.vue
touch src/components/Details.vue
touch src/components/Maps.vue

To register or create routes for the whole application navigation, create a router folder and index.js file.

mkdir src/router
touch src/router/index.js

Open and edit src/router/index.js then add these imports of VueRouter and the required created components.

import VueRouter from 'vue-router'
import List from '../components/List.vue'
import Details from '../components/Details.vue'
import Maps from '../components/Maps.vue'

Declare the routers for each component.

export default new VueRouter({
� � routes: [
� � � � {
� � � � � � path: '/',
� � � � � � name: 'List',
� � � � � � component: List
� � � � },
� � � � {
� � � � � � path: '/details/:id',
� � � � � � name: 'Details',
� � � � � � component: Details
� � � � },
� � � � {
� � � � � � path: '/maps/:lat/:lng',
� � � � � � name: 'Maps',
� � � � � � component: Maps
� � � � }
� � ]
})

Next, we need to add to the existing landing components. Open and edit src/App.vue then replace all templates and Vue script with this.

<template>
� <div id="app">
� � <router-view></router-view>
� </div>
</template>

<script>
export default {
� name: 'app'
}
</script>

<style>
#app {
� font-family: 'Avenir', Helvetica, Arial, sans-serif;
� -webkit-font-smoothing: antialiased;
� -moz-osx-font-smoothing: grayscale;
� text-align: center;
� color: #2c3e50;
� margin-top: 60px;
}
</style>

Finally, add or register this router file to src/main.js by adding these imports of VueRouter and router.

import VueRouter from 'vue-router'
import router from './router'

Register the vue-router after Vue.config.

Vue.use(VueRouter)

Modify new Vue to be like this.

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')
Step #4. Create an Array of Objects File

We will use the array of objects data dan get from the Javascript file. For that, create a new Javascript file in the root of the project folder.

touch src/Team.js

Open and edit that file then add these lines of the array.

export const Team = [
� � { id: 1, name: 'Arsenal', � �image: 'arsenal.png', location: 'London (Holloway)', stadium: � �'Emirates Stadium',
� � capacity: � �60704, manager: 'Mikel Arteta', captain: 'Pierre-Emerick Aubameyang', lat: 51.554929, lng: -0.108449 },
� � { id: 2, name: 'Aston Villa', � �image: 'aston-villa.png', location: � �'Birmingham', stadium: 'Villa Park',
� � capacity: 42785, manager: 'Dean Smith', captain: 'Jack Grealish', lat: 52.509131, lng: -1.884767 },
� � { id: 3, name: 'Bournemouth', � �image: 'bornemouth.png', � �location: � �'Bournemouth', stadium: � �'Dean Court',
� � capacity: 11364, manager: 'Eddie Howe', captain: 'Simon Francis', lat: 50.735278, lng: -1.838290 },
� � { id: 4, name: 'Brighton & Hove Albion', � �image: 'brighton.png', location: 'Brighton', stadium: � �'Falmer Stadium',
� � capacity: 30666, manager: 'Graham Potter', captain: 'Lewis Dunk', lat: 50.861606, lng: -0.083716 },
� � { id: 5, name: 'Burnley', � �image: 'burnley.png', � �location: � �'Burnley', stadium: � �'Turf Moor',
� � capacity: 21944, manager: 'Sean Dyche', captain: 'Ben Mee', lat: 53.789017, lng: -2.230187 },
� � { id: 6, name: 'Chelsea', � �image: 'chelsea.png', � �location: � �'London (Fulham)', stadium: � �'Stamford Bridge',
� � capacity: 41631, manager: 'Frank Lampard', captain: 'C�sar Azpilicueta', lat: 51.481696, lng: -0.190957 },
� � { id: 7, name: 'Crystal Palace', � �image: 'crystal-palace.png', location: 'London (Selhurst)', stadium: 'Selhurst Park',
� � capacity: 26047, manager: 'Roy Hodgson', captain: 'Luka Milivojevi?', lat: 51.398281, lng: -0.085489 },
� � { id: 8, name: 'Everton', � �image: 'everton.png', � �location: � �'Liverpool (Walton)', stadium: 'Goodison Park',
� � capacity: 39221, manager: 'Marco Silva', captain: 'S�amus Coleman', lat: 53.438813, lng: -2.966331 },
� � { id: 9, name: 'Leicester City', � �image: 'leicester.png', location: 'Leicester', stadium: 'King Power Stadium',
� � capacity: � �32312, manager: 'Brendan Rodgers', captain: 'Wes Morgan', lat: 52.620399, lng: -1.142147 },
� � { id: 10, name: 'Liverpool', � �image: 'liverpool.png', location: 'Liverpool (Anfield)', stadium: 'Anfield',
� � capacity: 54074, manager: 'J�rgen Klopp', captain: 'Jordan Henderson', lat: 53.430846, lng: -2.960844 },
� � { id: 11, name: � �'Manchester City', � �image: 'manchester-city.png', location: 'Manchester',
� � stadium: 'City of Manchester Stadium', capacity: 55017, manager: 'Pep Guardiola', captain: 'David Silva',
� � lat: 53.483176, lng: -2.200427 },
� � { id: 12, name: � �'Manchester United', � �image: 'manchester-united.png', location: 'Manchester', stadium: 'Old Trafford',
� � capacity: 74879, manager: 'Ole Gunnar Solskj�r', captain: 'Ashley Young', lat: 53.463077, lng: -2.291334 },
� � { id: 13, name: � �'Newcastle United', � �image: 'newcastle-united.png', location: 'Newcastle', stadium: 'St James Park',
� � capacity: 52354, manager: 'Steve Bruce', captain: 'Jamaal Lascelles', lat: 54.975581, lng: -1.621661 },
� � { id: 14, name: � �'Norwich City', � �image: 'norwich-city.png', location: 'Norwich', stadium: � �'Carrow Road',
� � capacity: 27244, manager: 'Daniel Farke', captain: 'Grant Hanley', lat: 52.622219, lng: 1.309328 },
� � { id: 15, name: � �'Sheffield United', � �image: 'sheffield-united.png', location: 'Sheffield', stadium: 'Bramall Lane',
� � capacity: 32702, manager: 'Chris Wilder', captain: 'Billy Sharp', lat: 53.370374, lng: �-1.470880 },
� � { id: 16, name: � �'Southampton', � �image: 'southampton.png', location: 'Southampton', stadium: 'St Marys Stadium',
� � capacity: 32384, manager: 'Ralph Hasenh�ttl', captain: 'Pierre-Emile H�jbjerg', lat: 50.905860, lng: -1.390941 },
� � { id: 17, name: � �'Tottenham Hotspur', � �image: 'tottenham-hotspur.png', location: 'London (Tottenham)',
� � stadium: � �'Tottenham Hotspur Stadium', capacity: 62214, manager: 'Jos� Mourinho', captain: 'Hugo Lloris', lat: 51.604319, lng: -0.066381 },
� � { id: 18, name: � �'Watford', � �image: 'watford.png', location: 'Watford', stadium: 'Vicarage Road',
� � capacity: 20400, manager: 'Quique S�nchez Flores', captain: 'Troy Deeney', lat: 51.649959, lng: -0.401525 },
� � { id: 19, name: � �'West Ham United', � �image: 'westham-united.png', location: 'London (Stratford)', stadium: 'London Stadium',
� � capacity: 60000, manager: 'Manuel Pellegrini', captain: 'Mark Noble', lat: 51.538750, lng: -0.016625 },
� � { id: 20, name: � �'Wolverhampton Wanderers', � �image: 'wolverhampton.png', location: 'Wolverhampton',
� � stadium: 'Molineux Stadium', capacity: 32050, manager: 'Nuno Esp�rito Santo', captain: 'Conor Coady', lat: 52.590301, lng: -2.130418 }
];

Because there are local images for the team logo, add an img folder inside assets folder then paste the team logos from out GitHub.

Step #5. Show List of Data using Bootstrap-Vue

We will use the Bootstrap-Vue library as the UI/UX for this Vue.js web application. For that, type this command to install the Bootstrap-Vue module.

npm i bootstrap-vue

Next, open and edit src/main.js then add these imports of BootstrapVue and Bootstrap stylesheet.

import BootstrapVue from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Add this line after Vue.config.

Vue.use(BootstrapVue);

Now, open and edit src/components/List.vue then add these lines of codes that contain Bootstrap-Vue templates and a method to get the list of data from the array of objects file.

<template>
� <b-row>
� � <b-col cols="12">
� � � <h2>
� � � � English Premier League
� � � </h2>
� � � <b-table striped hover :items="teams" :fields="fields" @row-clicked="details"></b-table>
� � </b-col>
� </b-row>
</template>

<script>

import router from '../router'
import { Team } from '../Team'

export default {
� name: 'List',
� data () {
� � return {
� � � fields: [
� � � � � { key: 'id' },
� � � � � { key: 'name', label: 'Team Name', class: 'text-left', thClass: 'text-center' },
� � � � � { key: 'location', label: 'City', class: 'text-left', thClass: 'text-center' },
� � � � � { }
� � � ],
� � � teams: Team,
� � � errors: [],
� � }
� },
� created () {
� � this.teams = Team
� },
� methods: {
� � details (team) {
� � � router.push({ name: 'Details', params: { id: team.id }})
� � }
� }
}
</script>

<style>
� .table {
� � width: 96%;
� � margin: 0 auto;
� }
</style>
Step #6. Show Data Details using Bootstrap-Vue

From the list of teams, users can click the item of the list then navigate to the details page with the ID params. The ID should receive in the details page using Vue.js $route.params. Open and edit src/components/Details.vue then add these lines of codes that contain Bootstrap-Vue template components and a method to get single data from the array of objects by ID.

<template>
� <b-row>
� � <b-col cols="12">
� � � <h2>
� � � � Team Details
� � � </h2>
� � � <b-jumbotron bg-variant="white">
� � � � <template slot="header">
� � � � � {{team.name}}
� � � � </template>
� � � � <template slot="lead">
� � � � � <b-img :src="require('../assets/img/' + team.image)" fluid alt="Responsive image"></b-img><br>
� � � � � City: {{team.location}}<br>
� � � � � Stadium: {{team.stadium}}<br>
� � � � � Stadium Capacity: {{team.capacity}}<br>
� � � � � Manager: {{team.manager}}<br>
� � � � � Captain: {{team.captain}}<br>
� � � � </template>
� � � � <hr class="my-4">
� � � � <b-btn block class="maps-btn" variant="success" @click.stop="showMaps(team.lat, team.lng)">Show Maps</b-btn>
� � � </b-jumbotron>
� � </b-col>
� </b-row>
</template>

<script>

import router from '../router'
import { Team } from '../Team'

export default {
� name: 'Details',
� data () {
� � return {
� � � key: '',
� � � teams: Team,
� � � team: {}
� � }
� },
� created () {
� � const id = parseInt(this.$route.params.id, 0)
� � this.team = this.teams.find(x => x.id === id)
� },
� methods: {
� � showMaps (lat, lng) {
� � � router.push({
� � � � name: 'Maps',
� � � � params: { lat: lat, lng: lng }
� � � })
� � }
� }
}
</script>

<style>
� .jumbotron {
� � padding: 2rem;
� }
� .maps-btn {
� � margin-right: 20px;
� � width: 70px;
� }
</style>

Actually, the "find" or "filter" method of the array has an error with the current core-js module. The solution is to update the core-js module to the latest version by type this command.

npm i core-js

Now, we are using this working version of the core-js module.

"core-js": "^3.6.1"
Step #7. Show Google Maps

You can use your own or someone else Google Maps API Key to implementing Google Maps on the web page. To display Google Maps, first, we need to install the google-maps-api-loader module.

npm i google-maps-api-loader

Add a new component to load Google Maps.

touch src/components/GoogleMapLoader.vue

Add these lines of codes that required to load Google Maps.

<template>
� <div>
� � <div
� � � class="google-map"
� � � ref="googleMap"
� � ></div>
� � <template v-if="Boolean(this.google) && Boolean(this.map)">
� � � <slot
� � � � :google="google"
� � � � :map="map"
� � � />
� � </template>
� </div>
</template>

<script>
import GoogleMapsApiLoader from "google-maps-api-loader";

export default {
� props: {
� � mapConfig: Object,
� � apiKey: String
� },

� data() {
� � return {
� � � google: null,
� � � map: null
� � };
� },

� async mounted() {
� � const googleMapApi = await GoogleMapsApiLoader({
� � � apiKey: this.apiKey
� � });
� � this.google = googleMapApi;
� � this.initializeMap();
� },

� methods: {
� � initializeMap() {
� � � const mapContainer = this.$refs.googleMap;
� � � this.map = new this.google.maps.Map(mapContainer, this.mapConfig);
� � }
� }
};
</script>

<style scoped>
� .google-map {
� � width: 100%;
� � min-height: 600px;
� }
</style>

Next, implement Google Maps to the Maps component.

<template>
� <b-row>
� � <b-col cols="12">
� � � <h2>
� � � � Team Details
� � � </h2>
� � � <GoogleMapLoader
� � � � :mapConfig="mapConfig"
� � � � apiKey="AIzaSyBgZZK8umUqJn8d5CoIZqWPJ_qtMfqD9q0"
� � � >
� � � </GoogleMapLoader>
� � </b-col>
� </b-row>
</template>

<script>
import GoogleMapLoader from "./GoogleMapLoader";

export default {
� components: {
� � GoogleMapLoader
� },

� computed: {
� � mapConfig () {
� � � return {
� � � � mapSettings: {
� � � � � enableHighAccuracy: true,
� � � � � timeout: 5000,
� � � � � maximumAge: 0
� � � � },
� � � � zoom: 15,
� � � � center: { lat: parseInt(this.$route.params.lat, 0), lng: parseInt(this.$route.params.lng, 0) }
� � � }
� � },
� }
};
</script>
Step #8. Run and Test the Vue.js Web App

Right now, we will test all of the functions of the Vue.js web application. Just run again this web app.

npm run serve

And here it is, you can test navigate from the list to the map pages.



That it's, Vue.js Tutorial: Learn to Build a Web App Quickly. You can get the full source code from our GitHub.

Thanks!

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

What’s new in HTML6

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

How to build Vue.js JWT Authentication with Vuex and Vue Router

How to build Vue.js JWT Authentication with Vuex and Vue Router

In this tutorial, we’re gonna build a Vue.js with Vuex and Vue Router Application that supports JWT Authentication

In this tutorial, we’re gonna build a Vue.js with Vuex and Vue Router Application that supports JWT Authentication. I will show you:

  • JWT Authentication Flow for User Signup & User Login
  • Project Structure for Vue.js Authentication with Vuex & Vue Router
  • How to define Vuex Authentication module
  • Creating Vue Authentication Components with Vuex Store & VeeValidate
  • Vue Components for accessing protected Resources
  • How to add a dynamic Navigation Bar to Vue App

Let’s explore together.

Contents

Overview of Vue JWT Authentication example

We will build a Vue application in that:

  • There are Login/Logout, Signup pages.
  • Form data will be validated by front-end before being sent to back-end.
  • Depending on User’s roles (admin, moderator, user), Navigation Bar changes its items automatically.

Screenshots

– Signup Page:

– Login Page & Profile Page (for successful Login):

– Navigation Bar for Admin account:

Demo

This is full Vue JWT Authentication App demo (with form validation, check signup username/email duplicates, test authorization with 3 roles: Admin, Moderator, User). In the video, we use Spring Boot for back-end REST APIs.

Flow for User Registration and User Login

For JWT Authentication, we’re gonna call 2 endpoints:

  • POST api/auth/signup for User Registration
  • POST api/auth/signin for User Login

You can take a look at following flow to have an overview of Requests and Responses Vue Client will make or receive.

Vue Client must add a JWT to HTTP Authorization Header before sending request to protected resources.

Vue App Component Diagram with Vuex & Vue Router

Now look at the diagram below.

Let’s think about it.

– The App component is a container with Router. It gets app state from Vuex store/auth. Then the navbar now can display based on the state. App component also passes state to its child components.

Login & Register components have form for submission data (with support of vee-validate). We call Vuex store dispatch() function to make login/register actions.

– Our Vuex actions call auth.service methods which use axios to make HTTP requests. We also store or get JWT from Browser Local Storage inside these methods.

Home component is public for all visitor.

Profile component get user data from its parent component and display user information.

BoardUser, BoardModerator, BoardAdmin components will be displayed by Vuex state user.roles. In these components, we use user.service to get protected resources from API.

user.service uses auth-header() helper function to add JWT to HTTP Authorization header. auth-header() returns an object containing the JWT of the currently logged in user from Local Storage.

Technology

We will use these modules:

  • vue: 2.6.10
  • vue-router: 3.0.3
  • vuex: 3.0.1
  • axios: 0.19.0
  • vee-validate: 2.2.15
  • bootstrap: 4.3.1
  • vue-fontawesome: 0.1.7
Project Structure

This is folders & files structure for our Vue application:

With the explaination in diagram above, you can understand the project structure easily.

Setup Vue App modules

Run following command to install neccessary modules:

npm install vue-router
npm install vuex
npm install [email protected]
npm install axios
npm install bootstrap jquery popper.js
npm install @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/vue-fontawesome

After the installation is done, you can check dependencies in package.json file.

"dependencies": {
  "@fortawesome/fontawesome-svg-core": "^1.2.25",
  "@fortawesome/free-solid-svg-icons": "^5.11.2",
  "@fortawesome/vue-fontawesome": "^0.1.7",
  "axios": "^0.19.0",
  "bootstrap": "^4.3.1",
  "core-js": "^2.6.5",
  "jquery": "^3.4.1",
  "popper.js": "^1.15.0",
  "vee-validate": "^2.2.15",
  "vue": "^2.6.10",
  "vue-router": "^3.0.3",
  "vuex": "^3.0.1"
},

Open src/main.js, add code below:

import Vue from 'vue';
import App from './App.vue';
import { router } from './router';
import store from './store';
import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import VeeValidate from 'vee-validate';
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import {
  faHome,
  faUser,
  faUserPlus,
  faSignInAlt,
  faSignOutAlt
} from '@fortawesome/free-solid-svg-icons';

library.add(faHome, faUser, faUserPlus, faSignInAlt, faSignOutAlt);

Vue.config.productionTip = false;

Vue.use(VeeValidate);
Vue.component('font-awesome-icon', FontAwesomeIcon);

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app');

You can see that we import and apply in Vue object:
store for Vuex (implemented later in src/store)
router for Vue Router (implemented later in src/router.js)
bootstrap with CSS
vee-validate
vue-fontawesome for icons (used later in nav)

Create Services

We create two services in src/services folder:


services

auth-header.js

auth.service.js (Authentication service)

user.service.js (Data service)


Authentication service

The service provides three important methods with the help of axios for HTTP requests & reponses:

  • login(): POST {username, password} & save JWT to Local Storage
  • logout(): remove JWT from Local Storage
  • register(): POST {username, email, password}
import axios from 'axios';

const API_URL = 'http://localhost:8080/api/auth/';

class AuthService {
  login(user) {
    return axios
      .post(API_URL + 'signin', {
        username: user.username,
        password: user.password
      })
      .then(this.handleResponse)
      .then(response => {
        if (response.data.accessToken) {
          localStorage.setItem('user', JSON.stringify(response.data));
        }

        return response.data;
      });
  }

  logout() {
    localStorage.removeItem('user');
  }

  register(user) {
    return axios.post(API_URL + 'signup', {
      username: user.username,
      email: user.email,
      password: user.password
    });
  }

  handleResponse(response) {
    if (response.status === 401) {
      this.logout();
      location.reload(true);

      const error = response.data && response.data.message;
      return Promise.reject(error);
    }

    return Promise.resolve(response);
  }
}

export default new AuthService();

If login request returns 401 status (Unauthorized), that means, JWT was expired or no longer valid, we will logout the user (remove JWT from Local Storage).

Data service

We also have methods for retrieving data from server. In the case we access protected resources, the HTTP request needs Authorization header.

Let’s create a helper function called authHeader() inside auth-header.js:

export default function authHeader() {
  let user = JSON.parse(localStorage.getItem('user'));

  if (user && user.accessToken) {
    return { Authorization: 'Bearer ' + user.accessToken };
  } else {
    return {};
  }
}

It checks Local Storage for user item.
If there is a logged in user with accessToken (JWT), return HTTP Authorization header. Otherwise, return an empty object.

Now we define a service for accessing data in user.service.js:

import axios from 'axios';
import authHeader from './auth-header';

const API_URL = 'http://localhost:8080/api/test/';

class UserService {
  getPublicContent() {
    return axios.get(API_URL + 'all');
  }

  getUserBoard() {
    return axios.get(API_URL + 'user', { headers: authHeader() });
  }

  getModeratorBoard() {
    return axios.get(API_URL + 'mod', { headers: authHeader() });
  }

  getAdminBoard() {
    return axios.get(API_URL + 'admin', { headers: authHeader() });
  }
}

export default new UserService();

You can see that we add a HTTP header with the help of authHeader() function when requesting authorized resource.

Define Vuex Authentication module

We put Vuex module for authentication in src/store folder.


store

auth.module.js (authentication module)

index.js (Vuex Store that contains also modules)


Now open index.js file, import auth.module to main Vuex Store here.

import Vue from 'vue';
import Vuex from 'vuex';

import { auth } from './auth.module';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    auth
  }
});

Then we start to define Vuex Authentication module that contains:

  • state: { status, user }
  • actions: { login, logout, register }
  • mutations: { loginSuccess, loginFailure, logout, registerSuccess, registerFailure }

We use AuthService which is defined above to make authentication requests.

auth.module.js

import AuthService from '../services/auth.service';

const user = JSON.parse(localStorage.getItem('user'));
const initialState = user
  ? { status: { loggedIn: true }, user }
  : { status: {}, user: null };

export const auth = {
  namespaced: true,
  state: initialState,
  actions: {
    login({ commit }, user) {
      return AuthService.login(user).then(
        user => {
          commit('loginSuccess', user);
          return Promise.resolve(user);
        },
        error => {
          commit('loginFailure');
          return Promise.reject(error.response.data);
        }
      );
    },
    logout({ commit }) {
      AuthService.logout();
      commit('logout');
    },
    register({ commit }, user) {
      return AuthService.register(user).then(
        response => {
          commit('registerSuccess');
          return Promise.resolve(response.data);
        },
        error => {
          commit('registerFailure');
          return Promise.reject(error.response.data);
        }
      );
    }
  },
  mutations: {
    loginSuccess(state, user) {
      state.status = { loggedIn: true };
      state.user = user;
    },
    loginFailure(state) {
      state.status = {};
      state.user = null;
    },
    logout(state) {
      state.status = {};
      state.user = null;
    },
    registerSuccess(state) {
      state.status = {};
    },
    registerFailure(state) {
      state.status = {};
    }
  }
};

You can find more details about Vuex at Vuex Guide.

Create Vue Authentication Components

Define User model

To make code clear and easy to read, we define the User model first.
Under src/models folder, create user.js like this.

export default class User {
  constructor(username, email, password) {
    this.username = username;
    this.email = email;
    this.password = password;
  }
}

Let’s continue with Authentication Components.
Instead of using axios or AuthService directly, these Components should work with Vuex Store:
– getting status with this.$store.state.auth
– making request by dispatching an action: this.$store.dispatch()


views

Login.vue

Register.vue

Profile.vue


Vue Login Page

In src/views folder, create Login.vue file with following code:

<template>
  <div class="col-md-12">
    <div class="card card-container">
      <img
        id="profile-img"
        src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
        class="profile-img-card"
      />
      <form name="form" @submit.prevent="handleLogin">
        <div class="form-group">
          <label for="username">Username</label>
          <input
            type="text"
            class="form-control"
            name="username"
            v-model="user.username"
            v-validate="'required'"
          />
          <div
            class="alert alert-danger"
            role="alert"
            v-if="errors.has('username')"
          >Username is required!</div>
        </div>
        <div class="form-group">
          <label for="password">Password</label>
          <input
            type="password"
            class="form-control"
            name="password"
            v-model="user.password"
            v-validate="'required'"
          />
          <div
            class="alert alert-danger"
            role="alert"
            v-if="errors.has('password')"
          >Password is required!</div>
        </div>
        <div class="form-group">
          <button class="btn btn-primary btn-block" :disabled="loading">
            <span class="spinner-border spinner-border-sm" v-show="loading"></span>
            <span>Login</span>
          </button>
        </div>
        <div class="form-group">
          <div class="alert alert-danger" role="alert" v-if="message">{{message}}</div>
        </div>
      </form>
    </div>
  </div>
</template>

<script>
import User from '../models/user';

export default {
  name: 'login',
  computed: {
    loggedIn() {
      return this.$store.state.auth.status.loggedIn;
    }
  },
  data() {
    return {
      user: new User('', ''),
      loading: false,
      message: ''
    };
  },
  mounted() {
    if (this.loggedIn) {
      this.$router.push('/profile');
    }
  },
  methods: {
    handleLogin() {
      this.loading = true;
      this.$validator.validateAll();

      if (this.errors.any()) {
        this.loading = false;
        return;
      }

      if (this.user.username && this.user.password) {
        this.$store.dispatch('auth/login', this.user).then(
          () => {
            this.$router.push('/profile');
          },
          error => {
            this.loading = false;
            this.message = error.message;
          }
        );
      }
    }
  }
};
</script>

<style scoped>
label {
  display: block;
  margin-top: 10px;
}

.card-container.card {
  max-width: 350px !important;
  padding: 40px 40px;
}

.card {
  background-color: #f7f7f7;
  padding: 20px 25px 30px;
  margin: 0 auto 25px;
  margin-top: 50px;
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-radius: 2px;
  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
}

.profile-img-card {
  width: 96px;
  height: 96px;
  margin: 0 auto 10px;
  display: block;
  -moz-border-radius: 50%;
  -webkit-border-radius: 50%;
  border-radius: 50%;
}
</style>

This page has a Form with username & password. We use [VeeValidate 2.x](http://<a href=) to validate input before submitting the form. If there is an invalid field, we show the error message.

We check user logged in status using Vuex Store: this.$store.state.auth.status.loggedIn. If the status is true, we use Vue Router to direct user to Profile Page:

created() {
  if (this.loggedIn) {
    this.$router.push('/profile');
  }
},

In the handleLogin() function, we dispatch 'auth/login' Action to Vuex Store. If the login is successful, go to Profile Page, otherwise, show error message.

Vue Register Page

This page is similar to Login Page.

For form validation, we have some more details:

  • username: required|min:3|max:20
  • email: required|email|max:50
  • password: required|min:6|max:40

For form submission, we dispatch 'auth/register' Vuex Action.

src/views/Register.vue

<template>
  <div class="col-md-12">
    <div class="card card-container">
      <img
        id="profile-img"
        src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
        class="profile-img-card"
      />
      <form name="form" @submit.prevent="handleRegister">
        <div v-if="!successful">
          <div class="form-group">
            <label for="username">Username</label>
            <input
              type="text"
              class="form-control"
              name="username"
              v-model="user.username"
              v-validate="'required|min:3|max:20'"
            />
            <div
              class="alert-danger"
              v-if="submitted && errors.has('username')"
            >{{errors.first('username')}}</div>
          </div>
          <div class="form-group">
            <label for="email">Email</label>
            <input
              type="email"
              class="form-control"
              name="email"
              v-model="user.email"
              v-validate="'required|email|max:50'"
            />
            <div
              class="alert-danger"
              v-if="submitted && errors.has('email')"
            >{{errors.first('email')}}</div>
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input
              type="password"
              class="form-control"
              name="password"
              v-model="user.password"
              v-validate="'required|min:6|max:40'"
            />
            <div
              class="alert-danger"
              v-if="submitted && errors.has('password')"
            >{{errors.first('password')}}</div>
          </div>
          <div class="form-group">
            <button class="btn btn-primary btn-block">Sign Up</button>
          </div>
        </div>
      </form>

      <div
        class="alert"
        :class="successful ? 'alert-success' : 'alert-danger'"
        v-if="message"
      >{{message}}</div>
    </div>
  </div>
</template>

<script>
import User from '../models/user';

export default {
  name: 'register',
  computed: {
    loggedIn() {
      return this.$store.state.auth.status.loggedIn;
    }
  },
  data() {
    return {
      user: new User('', '', ''),
      submitted: false,
      successful: false,
      message: ''
    };
  },
  mounted() {
    if (this.loggedIn) {
      this.$router.push('/profile');
    }
  },
  methods: {
    handleRegister() {
      this.message = '';
      this.submitted = true;
      this.$validator.validate().then(valid => {
        if (valid) {
          this.$store.dispatch('auth/register', this.user).then(
            data => {
              this.message = data.message;
              this.successful = true;
            },
            error => {
              this.message = error.message;
              this.successful = false;
            }
          );
        }
      });
    }
  }
};
</script>

<style scoped>
label {
  display: block;
  margin-top: 10px;
}

.card-container.card {
  max-width: 350px !important;
  padding: 40px 40px;
}

.card {
  background-color: #f7f7f7;
  padding: 20px 25px 30px;
  margin: 0 auto 25px;
  margin-top: 50px;
  -moz-border-radius: 2px;
  -webkit-border-radius: 2px;
  border-radius: 2px;
  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
}

.profile-img-card {
  width: 96px;
  height: 96px;
  margin: 0 auto 10px;
  display: block;
  -moz-border-radius: 50%;
  -webkit-border-radius: 50%;
  border-radius: 50%;
}
</style>

Profile Page

This page gets current User from Vuex Store and show information. If the User is not logged in, it directs to Login Page.

src/views/Profile.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>
        <strong>{{currentUser.username}}</strong> Profile
      </h3>
    </header>
    <p>
      <strong>Token:</strong>
      {{currentUser.accessToken.substring(0, 20)}} ... {{currentUser.accessToken.substr(currentUser.accessToken.length - 20)}}
    </p>
    <p>
      <strong>Id:</strong>
      {{currentUser.id}}
    </p>
    <p>
      <strong>Email:</strong>
      {{currentUser.email}}
    </p>
    <strong>Authorities:</strong>
    <ul>
      <li v-for="(role,index) in currentUser.roles" :key="index">{{role}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'profile',
  computed: {
    currentUser() {
      return this.$store.state.auth.user;
    }
  },
  mounted() {
    if (!this.currentUser) {
      this.$router.push('/login');
    }
  }
};
</script>

Create Vue Components for accessing Resources

These components will use UserService to request data.


views

Home.vue

BoardAdmin.vue

BoardModerator.vue

BoardUser.vue


Home Page

This is a public page.

src/views/Home.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>{{content}}</h3>
    </header>
  </div>
</template>

<script>
import UserService from '../services/user.service';

export default {
  name: 'home',
  data() {
    return {
      content: ''
    };
  },
  mounted() {
    UserService.getPublicContent().then(
      response => {
        this.content = response.data;
      },
      error => {
        this.content = error.response.data.message;
      }
    );
  }
};
</script>

Role-based Pages

We have 3 pages for accessing protected data:

  • BoardUser page calls UserService.getUserBoard()
  • BoardModerator page calls UserService.getModeratorBoard()
  • BoardAdmin page calls UserService.getAdminBoard()

This is an example, other Page are similar to this Page.

src/views/BoardUser.vue

<template>
  <div class="container">
    <header class="jumbotron">
      <h3>{{content}}</h3>
    </header>
  </div>
</template>

<script>
import UserService from '../services/user.service';

export default {
  name: 'user',
  data() {
    return {
      content: ''
    };
  },
  mounted() {
    UserService.getUserBoard().then(
      response => {
        this.content = response.data;
      },
      error => {
        this.content = error.response.data.message;
      }
    );
  }
};
</script>

Define Routes for Vue Router

Now we define all routes for our Vue Application.

src/router.js

import Vue from 'vue';
import Router from 'vue-router';
import Home from './views/Home.vue';
import Login from './views/Login.vue';
import Register from './views/Register.vue';

Vue.use(Router);

export const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/home',
      component: Home
    },
    {
      path: '/login',
      component: Login
    },
    {
      path: '/register',
      component: Register
    },
    {
      path: '/profile',
      name: 'profile',
      // lazy-loaded
      component: () => import('./views/Profile.vue')
    },
    {
      path: '/admin',
      name: 'admin',
      // lazy-loaded
      component: () => import('./views/BoardAdmin.vue')
    },
    {
      path: '/mod',
      name: 'moderator',
      // lazy-loaded
      component: () => import('./views/BoardModerator.vue')
    },
    {
      path: '/user',
      name: 'user',
      // lazy-loaded
      component: () => import('./views/BoardUser.vue')
    }
  ]
});

Add Navigation Bar to Vue App

This is the root container for our application that contains navigation bar. We will add router-view here.

src/App.vue

<template>
  <div id="app">
    <nav class="navbar navbar-expand navbar-dark bg-dark">
      <a href="#" class="navbar-brand">bezKoder</a>
      <div class="navbar-nav mr-auto">
        <li class="nav-item">
          <a href="/home" class="nav-link">
            <font-awesome-icon icon="home" /> Home
          </a>
        </li>
        <li class="nav-item" v-if="showAdminBoard">
          <a href="/admin" class="nav-link">Admin Board</a>
        </li>
        <li class="nav-item" v-if="showModeratorBoard">
          <a href="/mod" class="nav-link">Moderator Board</a>
        </li>
        <li class="nav-item">
          <a href="/user" class="nav-link" v-if="currentUser">User</a>
        </li>
      </div>

      <div class="navbar-nav ml-auto" v-if="!currentUser">
        <li class="nav-item">
          <a href="/register" class="nav-link">
            <font-awesome-icon icon="user-plus" /> Sign Up
          </a>
        </li>
        <li class="nav-item">
          <a href="/login" class="nav-link">
            <font-awesome-icon icon="sign-in-alt" /> Login
          </a>
        </li>
      </div>

      <div class="navbar-nav ml-auto" v-if="currentUser">
        <li class="nav-item">
          <a href="/profile" class="nav-link">
            <font-awesome-icon icon="user" />
            {{currentUser.username}}
          </a>
        </li>
        <li class="nav-item">
          <a href class="nav-link" @click="logOut">
            <font-awesome-icon icon="sign-out-alt" /> LogOut
          </a>
        </li>
      </div>
    </nav>

    <div class="container">
      <router-view />
    </div>
  </div>
</template>

<script>
export default {
  computed: {
    currentUser() {
      return this.$store.state.auth.user;
    },
    showAdminBoard() {
      if (this.currentUser) {
        return this.currentUser.roles.includes('ROLE_ADMIN');
      }

      return false;
    },
    showModeratorBoard() {
      if (this.currentUser) {
        return this.currentUser.roles.includes('ROLE_MODERATOR');
      }

      return false;
    }
  },
  methods: {
    logOut() {
      this.$store.dispatch('auth/logout');
      this.$router.push('/login');
    }
  }
};
</script>

Our navbar looks more professional when using font-awesome-icon.
We also make the navbar dynamically change by current User’s roles which are retrieved from Vuex Store state.

Handle Unauthorized Access

If you want to check Authorized status everytime a navigating action is trigger, just add router.beforeEach() at the end of src/router.js like this:

router.beforeEach((to, from, next) => {
  const publicPages = ['/login', '/home'];
  const authRequired = !publicPages.includes(to.path);
  const loggedIn = localStorage.getItem('user');

  // try to access a restricted page + not logged in
  if (authRequired && !loggedIn) {
    return next('/login');
  }

  next();
});

Conclusion

Congratulation!

Today we’ve done so many interesting things. I hope you understand the overall layers of our Vue application, and apply it in your project at ease. Now you can build a front-end app that supports JWT Authentication with Vue.js, Vuex and Vue Router.

Happy learning, see you again!

Performance Boost Using Highcharts.js and Vue.js

Performance Boost Using Highcharts.js and Vue.js

How to Maintain Performance with Big Datasets Using Highcharts. js and Vue. Sometimes you just need to show big datasets in your project. However, the library that you've used so far, as soon as you start to add data, becomes clunky and slow.

Sometimes you just need to show big datasets in your project. However, the library that you’ve used so far, as soon as you start to add data, becomes clunky and slow. You start to optimize your code, make cuts, or simplifications in datasets… In this article I’m going to show you that it does not have to be that way

Used by “80% of the largest companies in the world”, Highcharts is a powerful tool that allows you to plot massive series of data in a dynamic manner. As it’s free for non-commercial use, you can easily check for yourself if it’s going to suit your tastes. This project will also use highcharts-vue, which is a wrapper for Highcharts.

Installation:

The tools that we are going to need for the job are:

  • Highcharts
  • Highcharts vue
  • Axios

You can easily install them all at once using npm command:

npm i highcharts highcharts-vue axios -S

Now that we have all the pieces, let’s add them in the main.js file:

import Highcharts from "highcharts";
import HighchartsVue from "highcharts-vue";
import boost from "highcharts/modules/boost";

Vue.use(HighchartsVue);
boost(Highcharts);

You may be wondering, what this boost module is. I think that, to best explain it, the official documentation can be cited:

Boost is a stripped-down renderer-in-a-module for Highcharts and Highstock. It bypasses some of the standard Highcharts features (such as animation), and focuses on pushing as many points as possible as quickly as possible.

So, to sum it up, this module will make our charts run faster, but at the cost of some minor drawbacks. For example, you won’t be able to use dotted lines for your series. But, don’t you worry, the key parts are still fully functional.

Disclaimer: As the boost module is still in development, it can cause several bugs. For example, when using timestamps as x value, boost.useGPUTranslations will cause points to render in wrong places while zoomed, so you will need to do additional research, or just ask on the Highcharts forums.

Mocking the data:

As we need to have some data to show, and you probably don’t have any endpoints hanging around, we will need to generate some dummy data. We will need to create an object that has two (or more) attributes filled with chart data (for the sake of showing performance - about 100k points each, but you can totally try with 200-300k, or more). Highcharts allows us to add such as series in two ways:

  • array of {x,y} objects
  • array of values (where you specify the start of each series, as well as intervals)

This article covers how to make use of the first way, but you can always read the official Highcharts documentation to see other possibilities. To get the data, if you don’t want to generate it yourself, you can use my test.json (it’s in a public folder on the repo).

Let’s make use of it:

Now that we have some data to show, we can filtrate it. Even though this is a simplified version of the real application, we can create our architecture in a way that somewhat reflects the real case. Therefore, we can create 3 files:

  • ChartFiltration.vue
  • Chart.vue
  • store.js

ChartFiltration.vue will be, a parent of a Chart component. Its job will be issuing fetches and to know when the data is ready. Chart.vue will simply be used to show the series that are being passed by props. All logic related to processing the chart data will be stored in a store module.

First App.vue

<template>
  <div id="app">
    <ChartFiltration />
  </div>
</template>

<script>
import ChartFiltration from "./components/ChartFiltration";

export default {
  components: { ChartFiltration }
};
</script>

Nothing too fancy here, just some imports.

ChartFiltration.vue

<template>
  <div v-if="series">
    <Chart :series="series" />
  </div>
  <div v-else>
    Data is loading
  </div>
</template>

<script>
import { mapState } from "vuex";
import Chart from "./Chart";

export default {
  name: "ChartFiltration",
  components: { Chart },
  mounted() {
    this.$store.dispatch("fetchData");
  },
  computed: mapState({
    series: state => state.data
  })
};
</script>

As you can see, when the component is being mounted, the fetch in the store will be dispatched. The data will be mapped to a computed series. As I use conditional rendering, Chart.vue component will be rendered only when a series will have its length (because they will be returned as an array).

store.js

import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";

Vue.use(Vuex);

const RESET_ERROR = null;
const generateSeries = data => {
  return Object.keys(data).map(attribute => {
    return {
      type: "line",
      boostThreshold: 1,
      turboThreshold: 0,
      animation: false,
      name: attribute,
      data: data[attribute],
      marker: { enabled: true, radius: 3 },
      states: { hover: { lineWidthPlus: 0 } }
    };
  });
};
export default new Vuex.Store({
  state: {
    data: null,
    error: null
  },
  mutations: {
    SET_DATA(state, data) {
      state.data = generateSeries(data);
    },
    SET_ERROR(state, error) {
      state.error = error;
    }
  },
  actions: {
    async fetchData({ commit }) {
      commit("SET_ERROR", RESET_ERROR);

      axios
        .get("/test.json")
        .then(res => {
          commit("SET_DATA", res.data);
        })
        .catch(err => {
          commit("SET_ERROR", err);
        });
    }
  }
});

In this article I simply want to show how to work with data in Highcharts. There is no need to use modules for the store, like we would in a real application and, therefore, this is why I will use a single store file. Let’s focus on fetchData action. I simulate the http call with axios. After the data has been “fetched”, we will commit the SET_DATA mutation. Highcharts has its specific way of building chart objects. Because there can’t be too much logic in mutations, we add an additional helper function to process our response properly.

const generateSeries = data => {
  return Object.keys(data).map(attribute => {
    return {
      type: "line",
      boostThreshold: 1,
      turboThreshold: 0,
      animation: false,
      name: attribute,
      data: data[attribute],
      marker: { enabled: true, radius: 3 },
      states: { hover: { lineWidthPlus: 0 } }
    };
  });
};

The helper function simply iterates on object to get its attributes. After each iteration, we return the Highcharts series object, and this is what we will focus on:

  • type: here you can specify a type of series
  • boostThreshold: point threshold for when a series should enter boost mode. 1 means that we always want to use boost, 0 means never
  • turboThreshold: for a bigger {x,y} series, it’s better to disable it (by setting 0) as it will not render. If the set threshold is passed, only one dimensional arrays of numbers, or two dimensional arrays with x and y values, are allowed
  • animation: as animations slow down our chart, we don’t want them
  • name: name of the series shown in the legend
  • data: here, we need to assign our array of datapoints
  • marker: size of the markers on the chart
  • states: here, we will disable the line thickening on hover, as it will add lag for big datasets

What can we set additionally ?

  • color: remember that, when using the boost module, you can only use simple colors (rgb/hex). You can also set them with css, but it’s trickier.
  • Honestly, whatever you want, as this is a simple object that you will have access to by using this keyword in the chart object (for example, in the tooltip formatter). You can add units for series, or another type of name to show, but remember that you will need to handle logic for those by yourself.

Here is a list of attributes that you can officially assign for a series.

Now that we have our data processed, we can focus on showing it.

Chart.vue

<template>
  <highcharts :options="chartOptions" />
</template>
<script>
export default {
  name: "Chart",
  props: {
    series: { type: Array, required: true }
  },
  computed: {
    chartOptions() {
      return {
        chart: {
          type: "line",
          height: "700px",
          redraw: true,
          animation: false,
          zoomType: "xy",
          panning: true,
          panKey: "shift"
        },
        boost: { enabled: true },
        tooltip: {
          formatter() {
            const time = new Date(this.x).toJSON();
            const name = this.series.name;
            return `<b>${name}</b><br />
              value: ${this.y} <br />
              time: ${time}`;
          }
        },
        series: this.series,
        xAxis: {
          type: "datetime"
        }
      };
    }
  }
};
</script>

This components sole function is to create a chartOptions object that is passed to the highcharts-vue wrapper. We pass the series as props. On each change, a new chartOptions object will be computed and will force the whole component to rerender. Let’s look at the function that creates it:

chartOptions() {
      return {
        chart: {
          type: "line",
          height: "700px",
          animation: false,
          zoomType: "xy",
          panning: true,
          panKey: "shift"
        },
        boost: { enabled: true },
        tooltip: {
          formatter() {
            const time = new Date(this.x).toJSON();
            const name = this.series.name;
            return `<b>${name}</b><br />
              value: ${this.y} <br />
              time: ${time}`;
          }
        },
        series: this.series,
        xAxis: {
          type: "datetime"
        }
      };
    }

  • chart: has basic information about chart.
    • type: specifies which type of chart we are going to show
    • height: we can simply specify how much height in px this chart is going to have
    • animation: disables all animations which may slow down the chart
    • zoomType: specifies what type of zoom we can use. ‘xy’ means that we can zoom in both directions
    • panning: true allows us to pan the chart after zooming
    • panKey: specifies which key is used for panning
  • boost: {enabled: true} : this is used to tell the chart to use the boost module
  • tooltip: the tooltip is generated by default, but by using formatter(), we can specify exactly how it will look. Here, we have access (by this keyword) for each series attribute.
  • series: this is where we put the processed array of highcharts objects
  • xAxis: it’s used for informing chart, what is the type of series, specifying title, label format, and lots of other things.

The list of values that can be set for any of those attributes can be found here.

And, honestly, this is all. By moving processing logic to the store, we won’t cause any unnecessary computations inside of the component, which will improve speed immensely. In ChartFiltrations.vue, we can add some buttons to add additional filtrations to the chart. For example, we can add buttons to specify if we want to see only one type of attribute (remember that we need to still do the filtrations inside the store). Additionally, as in this example, we have both current and voltage. As you should know, they have different units, so to make our chart more lucid, we can use multiple y axises. It can be done simply by adding a yAxis object:

  yAxis: [
    {
      title: { text: 'Current [A]' },
      gridLineWidth: 0.5,
      showEmpty: false
    },
    {
      title: { text: 'Voltage [V]' },
      gridLineWidth: 0.5,
      showEmpty: false
    }
  ]

to the chartOptions object, specifying to which y axis the dataset belongs while creating highcharts series objects:

const generateSeries = data => {
  return Object.keys(data).map(attribute => {
    return {
      type: "line",
      yAxis: attribute === "current" ? 0 : 1
    };
  });
};

Let’s see how it looks in action:

As you can see, we had two series of 100k points, and they are working smoothly. In our project, sometimes we have 1 mln points to show at once, and performance is still superb. So, we can say with certainty that Highcharts fulfills its role, for sure.

In this article, I have hopefully shown you that lots of data does not come with a need to make performance cuts. This implementation is only one of numerous approaches to this particular topic. If you have any comments or questions, I will be happy to answer them through the comments.

You can find a working example here.

Vue vs. React: Why I choose Vue.js

Vue vs. React: Why I choose Vue.js

Vue.js is a great framework but so is React. So why did I choose to go to Vue.js rather than React? In this video I discuss why I think Vue.js is the better option over React.

Vue.js is a great framework but so is React. So why did I choose to go to Vue rather than React? In this video I discuss why I think Vue.js is the better option over React.