Create Vue Charts using a Vue Chart component for ApexCharts

Create Vue Charts using a Vue Chart component for ApexCharts

Vue.js component for ApexCharts - Build Modern Charts. Vue.js wrapper for ApexCharts to build interactive visualizations in vue.

Vue.js wrapper for ApexCharts to build interactive visualizations in vue.

Download and Installation
Installing via npm
source-shell
npm install --save apexcharts
npm install --save vue-apexcharts
Usage
source-js
import VueApexCharts from 'vue-apexcharts'
Vue.use(VueApexCharts)

Vue.component('apexchart', VueApexCharts)

To create a basic bar chart with minimal configuration, write as follows:

text-html-vue
<template>
   <div>
     <apexchart width="500" type="bar" :options="chartOptions" :series="series"></apexchart>
   </div>
</template>
source-js
export default {
    data: function() {
      return {
        chartOptions: {
          chart: {
            id: 'vuechart-example'
          },
          xaxis: {
            categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998]
          }
        },
        series: [{
          name: 'series-1',
          data: [30, 40, 35, 50, 49, 60, 70, 91]
        }]
      }
    },
};

This will render the following chart

How do I update the chart?

Simple! Just change the series or any option and it will automatically re-render the chart.
Click on the below example to see this in action

text-html-vue
<template>
   <div class="app">
     <apexchart width="550" type="bar" :options="chartOptions" :series="series"></apexchart>
     <div>
       <button @click="updateChart">Update!</button>
    </div>
   </div>

</template>
source-js
export default {
    data: function() {
      return {
        chartOptions: {
          chart: {
            id: 'vuechart-example',
          },
          xaxis: {
            categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998],
          },
        },
        series: [{
          name: 'series-1',
          data: [30, 40, 45, 50, 49, 60, 70, 81]
        }]
      }
    },
    methods: {
      updateChart() {
        const max = 90;
        const min = 20;
        const newData = this.series[0].data.map(() => {
          return Math.floor(Math.random() * (max - min + 1)) + min
        })

        const colors = ['#008FFB', '#00E396', '#FEB019', '#FF4560', '#775DD0']

        // Make sure to update the whole options config and not just a single property to allow the Vue watch catch the change.
        this.chartOptions = {
          colors: [colors[Math.floor(Math.random()*colors.length)]]
        };
        // In the same way, update the series option
        this.series = [{
          data: newData
        }]
      }
    }
};

Important: While updating the options, make sure to update the outermost property even when you need to update the nested property.

✅ Do this

source-js
this.chartOptions = {...this.chartOptions, ...{
    xaxis: {
        labels: {
           style: {
             colors: ['red']
           }
        }
    }
}}

❌ Not this

source-js
this.chartOptions.xaxis = {
    labels: {
        style: {
          colors: ['red']
        }
    }
}}
Props
Prop Type Description
series* Array The series is an array which accepts an object in the following format. To know more about the format of dataSeries, checkout Series docs on the website.
type* String line, area, bar, pie, donut, scatter, bubble, heatmap, radialBar, candlestick
width Number/String Possible values for width can be 100% or 400px or 400
height Number/String Possible values for height can be 100% or 300px or 300
options Object The configuration object, see options on API (Reference)
Methods

You don't actually need to call updateSeries() or updateOptions() manually. Changing the props will automatically update the chart. You only need to call these methods to update the chart forcefully.

Method Description
updateSeries Allows you to update the series array overriding the existing one
updateOptions Allows you to update the configuration object
toggleSeries Allows you to toggle the visibility of series programatically. Useful when you have custom legend.
appendData Allows you to append new data to the series array.
addText The addText() method can be used to draw text after chart is rendered.
addXaxisAnnotation Draw x-axis annotations after chart is rendered.
addYaxisAnnotation Draw y-axis annotations after chart is rendered.
addPointAnnotation Draw point (xy) annotations after chart is rendered.

How to call the methods mentioned above?

text-html-basic
<template>
  <div class="example">
    <apexchart ref="demoChart" width="500" :options="chartOptions" :series="series"></apexchart>
  </div>
</template>

<script>
  functionName: function() {
    this.$refs.demoChart.updateOptions({ colors: newColors })
  },
</script>
How to call methods of ApexCharts without referencing the chart element?

Sometimes, you may want to call methods of the core ApexCharts library from some other place, and you can do so on this.$apexcharts global variable directly. You need to target the chart by chart.id while calling this method

Example

source-js
this.$apexcharts.exec('vuechart-example', 'updateSeries', [{
  data: [40, 55, 65, 11, 23, 44, 54, 33]
}])

In the above method, vuechart-example is the ID of chart, updateSeries is the name of the method you want to call and the third parameter is the new Series you want to update.

More info on the .exec() method can be found here

All other methods of ApexCharts can be called the same way.

What's included

The repository includes the following files and directories.

vue-apexcharts/
├── dist/
│   └── vue-apexcharts.js
└── src/
    ├── ApexCharts.component.js
    ├── Utils.js
    └── index.js

Running the examples

Basic Examples are included to show how to get started using ApexCharts with Vue easily.

To run the examples,

source-shell
cd example
npm install
npm run serve
Development

Install dependencies

source-shell
npm install

Bundling

source-shell
npm run build

Github

apexcharts/vue-apexcharts

Learn How to Build Various Types of Charts using Vue.js

Learn How to Build Various Types of Charts using Vue.js

In this VueJS tutorial, learn how to build various types of charts using vue-chartjs, vue-plotley, and vue-apexcharts with Vue.js. Learn how to create stunning data visuals using chart engines vue-chartjs, vue-plotley, and vue-apexcharts with Vue.js.

When it comes to client-side charting libraries, developers have a wealth of options — some free, some paid, some focused on specific types of charts, and some with numerous different types.

I’d like to share my experience working with a few different chart engines and my favorite frontend framework, Vue.js. But before we dive in, let’s establish some ground rules:

  • For all the libraries, I’ll use the same set of JSON data. This will be “imaginative” data from a blog stats engine. I’m curious how much each chart solution will make me change my data to work properly.
  • For all the libraries I’m going to build — or attempt to build — I’ll use the same set of charts: a line chart, a bar chart with two sets of data, and a pie chart.
  • While not a deal-breaker, I want to see which projects will let me work with “non-Vue applications” — that’s my awkward way of describing support for folks who use Vue to progressively enhance a simple HTML page as opposed to building an entire application. It’s something of a pet peeve of mine; since Vue supports both scenarios, I like my code to support both as well.
The data

To support my three charts, I have a JSONBin.io endpoint at https://api.jsonbin.io/b/5e2b4f673d75894195de48ff/1 that looks like this:

{
  "pageViews": [
    { "date": "Jan2019", "views": 39101 },
    { "date": "Feb2019", "views": 42983 },
    { "date": "Mar2019", "views": 58741 },
    { "date": "Apr2019", "views": 48071 },
    { "date": "May2019", "views": 46391 },
    { "date": "Jun2019", "views": 62001 },
    { "date": "Jul2019", "views": 61829 },
    { "date": "Aug2019", "views": 22412 },
    { "date": "Sep2019", "views": 78547 },
    { "date": "Oct2019", "views": 72190 },
    { "date": "Nov2019", "views": 53291 },
    { "date": "Dec2019", "views": 69341 }
  ],
  "pageViewsByCategory": {
    "vue": 51290,
    "javascript": 89125,
    "movies": 10892,
    "music": 7901,
    "cats": 49901
  },
  "newVsReturning": [
    { "date": "Jul2019", "new": 1543, "old": 581 },
    { "date": "Aug2019", "new": 4713, "old": 374 },
    { "date": "Sep2019", "new": 6531, "old": 475 },
    { "date": "Oct2019", "new": 8031, "old": 913 },
    { "date": "Nov2019", "new": 6725, "old": 1032 },
    { "date": "Dec2019", "new": 4781, "old": 731 }
  ]
}

The first set of data represents 12 months of page views on a blog. This will be displayed in a line graph. The second set includes stats about page views for unique categories. I’ll use a pie chart for this.

Finally, there is a set of stats that represent whether or not a visitor is new or returning to the blog. For that, I’ll create a stacked bar chart in which each bar displays both values so you can compare and see the change over time. If stacking the bars proves difficult, two bars next to each other will do the trick, as long as you can still tell what you’re looking at.

Let’s look at some examples.

vue-chartjs

vue-chartjs is a wrapper for the Chart.js project. Chart.js is a powerful and beautiful charting library that includes myriad chart types — more than enough to satisfy my requirements for this project.

Installation supports both app- and script-based support, which covers all the bases. But because of how it works, my gut tells me this project will be much more appropriate for Vue apps than in script-based scenarios.

The library, at its core, doesn’t do a whole lot out of the box. The docs make this clear:

The idea behind vue-chartjs is to provide easy to use components, with maximum flexibility and extensibility. To achieve this, you need to create your own Chart Component and extend it with the provided vue-chartjs components.

So whereas I went into this thinking I’d simply drop a <pie> tag into my code, vue-chartjs requires you to build your own component first.

That isn’t necessarily a bad thing; it enforces a level of flexibility that you’ll appreciate later. But in my case, I had to take some extra time to figure things out.

I also had some difficulty determining what to pass. Since the library provides a minimal set of features out of the box, you need to know more about how Chart.js works. Again, I’m not saying this is bad, but if I had known this, I would have spent more time in Chart.js by itself first before looking at the library. I also wish the vue-chartjs docs included more complete demos.

As an example, the “Creating your first Chart” section of the docs doesn’t actually tell you everything you need to know. I would have provided more context and sample code here, ideally leading to a CodePen or CodeSandbox demo that users could run. Again, if you already know Chart.js, then this won’t be a problem. But it was for me.

Having said that, once I got past the initial learning hump, I was pretty darn impressed. Let’s look at the code I built.

First, the top-level component is responsible for getting my data and then using the components I built for my charts. Here’s the simple layout:

><template>
  <div id="app">
    <h2>Page Views</h2>
    <LineChart v-if="loaded" :data="pageViews" :styles="styles"/>
    <h2>Views by Category</h2>
    <PieChart v-if="loaded" :data="pageViewsByCategory" :styles="styles"/>
    <h2>New vs Returning Visitors</h2>
    <BarChart v-if="loaded" :data="visitors" :styles="styles"/>
  </div>
</template>

Each chart component is passed the data it needs and a consistent set of styles, and it will be displayed after my data is loaded.

Here’s the JavaScript code:

import LineChart from "./components/LineChart";
import PieChart from "./components/PieChart";
import BarChart from "./components/BarChart";

export default {
  name: "App",
  components: {
    LineChart,
    PieChart,
    BarChart
  },
  data() {
    return {
      loaded: false,
      pageViews: null,
      pageViewsByCategory: null,
      visitors: null,
      styles: {
        width: "650px",
        height: "400px",
        position: "relative"
      }
    };
  },
  async created() {
    let resp = await fetch(
      "https://api.jsonbin.io/b/5e2b4f673d75894195de48ff/1"
    );
    let data = await resp.json();
    this.pageViews = data.pageViews;
    this.pageViewsByCategory = data.pageViewsByCategory;
    this.visitors = data.newVsReturning;
    this.loaded = true;
  }
};

This simply hits the remote API (which is just a hosted JSON packet) and then assigns the resultant data to the appropriate place.

Now let’s look at the chart components.

<script>
import { Line } from "vue-chartjs";

export default {
  extends: Line,
  name: "LineChart",
  props: ["data"],
  mounted() {
    // reformat in the way the lib wants
    let chartData = {
      labels: [],
      datasets: [
        {
          label: "Page Views",
          backgroundColor: "#f87979",
          data: []
        }
      ]
    };

    for (let i = 0; i < this.data.length; i++) {
      chartData.labels.push(this.data[i].date);
      chartData.datasets[0].data.push(this.data[i].views);
    }

    this.renderChart(chartData, {
      responsive: true,
      maintainAspectRatio: false
    });
  }
};
</script>

Note that there isn’t a <template>. That’s because the component I wrap will handle the layout. My component takes in the original data and reformats it to the form Chart.js needs. To get this working, I consulted the Chart.js docs for each relevant chart type.

That’s basically it. My pie chart simply switched out how it handled the data.

<script>
import { Pie } from "vue-chartjs";

export default {
  extends: Pie,
  name: "PieChart",
  props: ["data"],
  mounted() {
    // color list below came from https://stackoverflow.com/a/45489530/52160
    // reformat in the way the lib wants
    let chartData = {
      labels: [],
      datasets: [
        {
          label: "Views by Category",
          data: [],
          backgroundColor: [
            "#0074D9",
            "#FF4136",
            "#2ECC40",
            "#FF851B",
            "#7FDBFF",
            "#B10DC9",
            "#FFDC00",
            "#001f3f",
            "#39CCCC",
            "#01FF70",
            "#85144b",
            "#F012BE",
            "#3D9970",
            "#111111",
            "#AAAAAA"
          ]
        }
      ]
    };

    for (let category in this.data) {
      chartData.labels.push(category);
      chartData.datasets[0].data.push(this.data[category]);
    }

    this.renderChart(chartData, {
      responsive: true,
      maintainAspectRatio: false
    });
  }
};
</script>

Note that Chart.js does not provide default coloring out of the box (as far as I know). That meant I needed to provide a list of colors for my chart. It would have been nice if the library had a default set of colors I could apply.

Finally, here’s the bar chart. By default, the chart isn’t stacked, but you can see the options at the bottom to enable that.

<script>
import { Bar } from "vue-chartjs";

export default {
  extends: Bar,
  name: "BarChart",
  props: ["data"],
  mounted() {
    // reformat in the way the lib wants
    let chartData = {
      labels: [],
      datasets: [
        {
          label: "New",
          backgroundColor: "#f87979",
          data: []
        },
        {
          label: "Old",
          backgroundColor: "#c0c0c0",
          data: []
        }
      ]
    };

    for (let i = 0; i < this.data.length; i++) {
      chartData.labels.push(this.data[i].date);
      chartData.datasets[0].data.push(this.data[i].new);
      chartData.datasets[1].data.push(this.data[i].old);
    }

    this.renderChart(chartData, {
      responsive: true,
      maintainAspectRatio: false,
      scales: {
        xAxes: [
          {
            stacked: true
          }
        ],
        yAxes: [
          {
            stacked: true
          }
        ]
      }
    });
  }
};
</script>

We could make all three of these components look nicer. For example, I hardcoded some of the labels.

How do they look? Let’s see:

Remember, I didn’t spend any time trying to make these pretty. You can find the full source code (and working demo) at CodeSandbox:

vue-plotly

Next up is [vue-plotly](https://github.com/David-Desmaisons/vue-plotly), which bills itself as a “thin wrapper” for Plotly, a charting library I had previously never heard of. vue-plotly only supports installation via npm, so you won’t be using this in script-based Vue applications.

Once installed, you only get one component, <Plotly>, which works with a few properties. This makes it really quick to get started. You can simply specify your data and layout properties (which can be blank when you start) and begin coding. While my vue-chartjs demo took a while to write, this one was a breeze.

I tried to follow the same pattern and layout from the previous example. First, let’s look at the HTML.

<template>
  <div>

    <h2>Page Views</h2>
    <Plotly v-if="loaded" 
    :data="pageViewData" 
    :layout="layout" 
    :display-mode-bar="false" />

  <h2>Views by Category</h2>
    <Plotly v-if="loaded"
    :data="pageViewsByCategoryData"
    :layout="layout"
    :display-mode-bar="false" />

  <h2>New vs Returning Visitors</h2>
    <Plotly v-if="loaded"
    :data="visitorsData"
    :layout="layout"
    :display-mode-bar="false" />

  </div>
</template>

As you can see, I’m using the same <Plotly> tag repeatedly and only changing out the data I’m binding to the chart. Now let’s look at the code.

import { Plotly } from 'vue-plotly'

export default {
    components: { Plotly },
    data() {
        return {
            loaded: false, 
            pageViewData:null, 
            pageViewsByCategoryData: null,
            visitorsData: null,
            layout: {
                autosize: false, 
                width:750,
                barmode:'stack'
            }
        }
    },
    async created() {
        let resp = await fetch("https://api.jsonbin.io/b/5e2b4f673d75894195de48ff/1");
        let data = await resp.json();

        let pageViews = data.pageViews;
        let pageViewsByCategory = data.pageViewsByCategory;
        let visitors = data.newVsReturning;

        // setup data for line chart
        this.pageViewData = [
            { x:[], y:[], type:'scatter' }
        ];

        pageViews.forEach(pv => {
            this.pageViewData[0].x.push(pv.date);
            this.pageViewData[0].y.push(pv.views);
        });

        // setup data for pie chart
        this.pageViewsByCategoryData = [
            {
                values: [],
                labels: [], 
                type: 'pie'
            }
        ];

        for(let cat in pageViewsByCategory) {
            this.pageViewsByCategoryData[0].labels.push(cat);
            this.pageViewsByCategoryData[0].values.push(pageViewsByCategory[cat]);
        }

        this.visitorsData = [
            { 
                x: [], 
                y: [],
                name:'New Users',
                type:'bar'
            },
            { 
                x: [], 
                y: [],
                name:'Old Users',
                type:'bar'
            }
        ]

        // setup data for stacked bar
        visitors.forEach(v => {
            this.visitorsData[0].x.push(v.date);
            this.visitorsData[0].y.push(v.new);
            this.visitorsData[1].x.push(v.date);
            this.visitorsData[1].y.push(v.old);
        });

        this.loaded = true;
    }
}

My core data contains null values for my chart data and one layout value that they all share. Note, however, that barmode only applies to the bar chart.

My created hits my fake API again and then, one by one, formats the data for each chart. The Plotly JavaScript docs are well-done and a bit more direct than Chart.js. Note that I didn’t have to specify any colors; I could have if I wanted to, but Plotly has some good defaults.

Here’s the finished product.

Unfortunately, the vue-plotly package had issues running on CodeSandbox. I fired up a copy on Zeit, though, and you can run it here.

vue-apexcharts


For my third and final charting library I used [vue-apexcharts](https://github.com/apexcharts/vue-apexcharts), a wrapper for the ApexChart.js charting library that, like vue-plotly, I had never heard of before. This library was the simplest for me to use — although, to be fair, I’d had plenty of practice building charts by this point.

AppChart.js is only installable via npm. Follow that up with a quick addition to your main.js file:

import VueApexCharts from "vue-apexcharts";
Vue.use(VueApexCharts);
Vue.component("apexchart", VueApexCharts);

Next, use the <apexcharts/> tag in your components.

Even though I found this library easiest to use, I still found the organization of the arguments to be counterintuitive. We’ll take a look at the sample, but first, the tag:

<apexchart width="500" type="bar" :options="chartOptions" :series="series"></apexchart>

So far, so good.

I like specifying the type and width as top-level attributes, but note the two properties chartOptions and series. Those values look like this:

chartOptions: {
  chart: {
    id: 'vuechart-example'
  },
  xaxis: {
    categories: [1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998]
  }
},
series: [{
  name: 'series-1',
  data: [30, 40, 35, 50, 49, 60, 70, 91]
}]
}

The axis information seems tied to the data values, but it’s in the options block as opposed to the data block. It might seem like I’m nitpicking, and I’m sure not all developers would agree, but this seems odd to me. Having said that, it didn’t really slow me down. Here’s how my layout turned out:

<h2>Page Views</h2>
<apexchart v-if="loaded" width="500" type="line" 
:options="pvOptions" :series="pvSeries"></apexchart>

<h2>Views by Category</h2>
<apexchart v-if="loaded" width="500" type="pie" 
:options="catOptions" :series="catSeries"></apexchart>

<h2>New vs Returning Visitors</h2>
<apexchart v-if="loaded" width="500" type="bar"
  :options="visitorOptions" :series="visitorSeries"></apexchart>

Very readable, very easy to understand. The code is pretty similar to the previous versions — it gets the data and then transforms it.

export default {
  name: "App",
  data() {
    return {
      loaded: false,
      pvOptions: null,
      pvSeries: null,
      catOptions: null,
      catSeries: null,
      visitorOptions: null,
      visitorSeries: null
    };
  },
  async created() {
    let resp = await fetch(
      "https://api.jsonbin.io/b/5e2b4f673d75894195de48ff/1"
    );
    let data = await resp.json();

    let pageViews = data.pageViews;
    let pageViewsByCategory = data.pageViewsByCategory;
    let visitors = data.newVsReturning;

    this.pvOptions = {
      xaxis: {
        categories: []
      }
    };

    this.pvSeries = [
      {
        name: "Page Views",
        data: []
      }
    ];

    pageViews.forEach(pv => {
      this.pvOptions.xaxis.categories.push(pv.date);
      this.pvSeries[0].data.push(pv.views);
    });

    this.catOptions = {
      labels: []
    };

    this.catSeries = [];

    for (let cat in pageViewsByCategory) {
      this.catOptions.labels.push(cat);
      this.catSeries.push(pageViewsByCategory[cat]);
    }

    this.visitorOptions = {
      chart: {
        stacked: true
      },
      xaxis: {
        categories: []
      }
    };

    this.visitorSeries = [
      {
        name: "New",
        data: []
      },
      {
        name: "Old",
        data: []
      }
    ];

    visitors.forEach(v => {
      this.visitorOptions.xaxis.categories.push(v.date);

      this.visitorSeries[0].data.push(v.new);
      this.visitorSeries[1].data.push(v.old);
    });

    this.loaded = true;
  }
};

The only time-consuming task was figuring out how to turn the last bar chart into a stacked bar chart — but I’m talking five minutes, not five hours.

Here’s how it came out. Notice I never specified any styling data at all, and I think they came out rather nice.

You can view the full source code and online demo at CodeSandbox:

Conclusion

I hope you find these examples interesting and helpful. You can find more at the [awesome-vue](https://github.com/vuejs/awesome-vue) repository, a list of Vue projects related to, well, everything. I selected my projects from the charts section.

Vue.js wrapper for Chart.js

Vue.js wrapper for Chart.js

Vue.js Chart.Js Wrapper To Create Reuseable Charts .vue-chartjs is a wrapper for Chart.js in vue. You can easily create reuseable chart components.

vue-chartjs is a wrapper for Chart.js in vue. You can easily create reuseable chart components.

Demo & Docs Compatibility
  • v1 later @legacy
    • Vue.js 1.x
  • v2 later
    • Vue.js 2.x

After the final release of vue.js 2, you also get the v2 by default if you install vue-chartjs over npm. No need for the @next tag anymore. If you want the v1 you need to define the version or use the legacy tag. If you're looking for v1 check this branch

Install
  • yarn install: yarn add vue-chartjs chart.js
  • npm install: npm install vue-chartjs chart.js --save

Or if you want to use it directly in the browser add

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
<script src="https://unpkg.com/vue-chartjs/dist/vue-chartjs.min.js"></script>

to your scripts. See Codepen

Browser

You can use vue-chartjs directly in the browser without any build setup. Like in this codepen. For this case, please use the vue-chartjs.min.js which is the minified version. You also need to add the Chart.js CDN script.

You can then simply register your component:

Vue.component('line-chart', {
  extends: VueChartJs.Line,
  mounted () {
    this.renderChart({
      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
      datasets: [
        {
          label: 'Data One',
          backgroundColor: '#f87979',
          data: [40, 39, 10, 40, 39, 80, 40]
        }
      ]
    }, {responsive: true, maintainAspectRatio: false})
  }
})
How to use

You need to import the component and then either use extends or mixins and add it.

You can import the whole package or each module individual.

import VueCharts from 'vue-chartjs'
import { Bar, Line } from 'vue-chartjs'

Just create your own component.

// CommitChart.js
import { Bar } from 'vue-chartjs'

export default {
  extends: Bar,
  mounted () {
    // Overwriting base render method with actual data.
    this.renderChart({
      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
      datasets: [
        {
          label: 'GitHub Commits',
          backgroundColor: '#f87979',
          data: [40, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11]
        }
      ]
    })
  }
}

or in TypeScript

// CommitChart.ts
import { Bar } from 'vue-chartjs';
import { Component } from 'vue-property-decorator';

@Component({
    extends: Bar, // this is important to add the functionality to your component
})
export default class CommitChart extends Vue<Bar> {
  mounted () {
    // Overwriting base render method with actual data.
    this.renderChart({
      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
      datasets: [
        {
          label: 'GitHub Commits',
          backgroundColor: '#f87979',
          data: [40, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11]
        }
      ]
    })
  }
}

Then simply import and use your own extended component and use it like a normal vue component

import CommitChart from 'path/to/component/CommitChart'
Another Example with options

You can overwrite the default chart options. Just pass the options object as a second parameter to the render method

// MonthlyIncome.vue
import { Line } from 'vue-chartjs'

export default {
  extends: Line,
  props: ['data', 'options'],
  mounted () {
    this.renderChart(this.data, this.options)
  }
}

Use it in your vue app

import MonthlyIncome from 'path/to/component/MonthlyIncome'

<template>
  <monthly-income :data={....} />
</template>

<script>
export default {
  components: { MonthlyIncome },
  ....
}
</script>
Reactivity

Chart.js does not update or re-render the chart if new data is passed. However, you can simply implement this on your own or use one of the two mixins which are included.

  • reactiveProp
  • reactiveData

Both are included in the mixins module.

The mixins automatically create chartData as a prop or data. And add a watcher. If data has changed, the chart will update. However, keep in mind the limitations of vue and javascript for mutations on arrays and objects. It is important that you pass your options in a local variable named options! The reason is that if the mixin re-renders the chart it calls this.renderChart(this.chartData, this.options) so don't pass in the options object directly or it will be ignored.

More info here

// MonthlyIncome.js
import { Line, mixins } from 'vue-chartjs'

export default {
  extends: Line,
  mixins: [mixins.reactiveProp],
  props: ['chartData', 'options'],
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
}

Mixins module

The mixins module is included in the VueCharts module and as a separate module. Some ways to import them:

// Load complete module with all charts
import VueCharts from 'vue-chartjs'

export default {
  extends: VueCharts.Line,
  mixins: [VueCharts.mixins.reactiveProp],
  props: ['chartData', 'options'],
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
}
// Load separate modules
import { Line, mixins } from 'vue-chartjs'

export default {
  extends: Line,
  mixins: [mixins.reactiveProp],
  props: ['chartData', 'options'],
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
}
// Load separate modules with destructure assign
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins

export default {
  extends: Line,
  mixins: [reactiveProp],
  props: ['chartData', 'options'],
  mounted () {
    this.renderChart(this.chartData, this.options)
  }
}
Single File Components

You can create your components in Vues single file components. However it is important that you do not have the <template></template> included. Because Vue can't merge templates. And the template is included in the mixin. If you leave the template tag in your component, it will overwrite the one which comes from the base chart and you will have a blank screen.

Available Charts

Bar Chart

Line Chart

Doughnut

Pie

Radar

Polar Area

Bubble

Scatter

Build Setup
# install dependencies
npm install

# serve with hot reload at localhost:8080
npm run dev

# build for production with minification
npm run build

# run unit tests
npm run unit

# run e2e tests
npm run e2e

# run all tests
npm test

For a detailed explanation of how things work, check out the guide and docs for vue-loader.

Contributing
  1. Fork it ( https://github.com/apertureless/vue-chartjs/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request
Download Details:

Author: apertureless

Official Website: https://github.com/apertureless/vue-chartjs

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!