How to Run and Use Watchers in Your Vue.js App

How to Run and Use Watchers in Your Vue.js App

Watchers in Vue.js is for watching changes in your component’s variables. It is useful because sometimes we want to do something as some of our components’ variables are in the middle of changing. In this post, we'll learn "How to Run and Use Watchers in Your Vue.js App"

How to Use Watchers in Your Vue.js App

Watchers in Vue.js is for watching changes in your component’s variables. It is useful because sometimes we want to do something as some of our components’ variables are in the middle of changing.

To use Watchers in Vue.js, we put our field into the watch property of our component’s object. When in there, we put our component’s property returned from the data property that we want to watch. For example, if we have a property called form in our data object, then our watch property will look something like:

form: {
  handler(val) {
    if (!this.form) {
      this.toSymbols = this.symbols;
      this.fromSymbols = this.symbols;
      return;
    }
    this.hasResult = false;
    if (this.form.from) {
      this.toSymbols = this.symbols.filter(s => s.code != this.form.from);
    }
    if (this.form.to) {
      this.fromSymbols = this.symbols.filter(s => s.code != this.form.to);
        }
    },
    deep: true
 }

This is an example of a deep watcher, which means changes in all properties in the object are being watched. If anything changes, the handler code will run.

In this story, we will build a currency converter. To get started building our app, we need the Vue CLI, which contains a development server, and scripts to generate boilerplate code. Run npm install -g @vue/cli to install Vue CLI globally.

Then run vue create currency-converter to generate the boilerplate code.

Now we are ready to start writing code. Since we are building a single-page app, we need a router to route URLs to our pages. Vue Router is the most commonly used router for Vue.js apps. In addition, we need form validation and a good looking UI. Vee-validate is a well-known form-validation library for Vue.js apps. It works with many form-validation cases, like checking for required fields and checking for required types of data like numbers.

We also will be displaying graphs in our app, so we want to use a library to make this an easy task. Vue-chartjs is a Vue.js wrapper for the well-known Chart.js library. We also need to get exchange rates from the internet to display and use in our app, so we need an HTTP client. SuperAgent is an HTTP client that fulfills our needs.

After that, we can run the development server to display our app by running npm run serve. It will refresh the browser automatically as we are updating our code.

We can install all of them by running npm i chart.js vue-chartjs vue-material vue-router.

Now that we have all our libraries installed, we can start writing the logic code. First, we start by writing some constants we will use in multiple places by adding a urls.js in the src folder.

Add the following to the file:

const APIURL = 'https://api.exchangeratesapi.io';
export { APIURL };

In main.js, put this code in:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'router.beforeEach((to, from, next) => {
  document.title = to.meta.title
  next()
})Vue.config.productionTip = false/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: { App }
})

This is the entry point of the app.

This block changes the title as we navigate to different pages by listening to Vue router’s navigation events:

router.beforeEach((to, from, next) => {
  document.title = to.meta.title
  next()
})

Also, we need a list of currencies for people to select:

export default {
    "USD": {
        "symbol": "$",
        "name": "US Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "USD",
        "name_plural": "US dollars"
    },
    "CAD": {
        "symbol": "CA$",
        "name": "Canadian Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "CAD",
        "name_plural": "Canadian dollars"
    },
    "EUR": {
        "symbol": "€",
        "name": "Euro",
        "symbol_native": "€",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "EUR",
        "name_plural": "euros"
    },
    "AED": {
        "symbol": "AED",
        "name": "United Arab Emirates Dirham",
        "symbol_native": "د.إ.‏",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "AED",
        "name_plural": "UAE dirhams"
    },
    "AFN": {
        "symbol": "Af",
        "name": "Afghan Afghani",
        "symbol_native": "؋",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "AFN",
        "name_plural": "Afghan Afghanis"
    },
    "ALL": {
        "symbol": "ALL",
        "name": "Albanian Lek",
        "symbol_native": "Lek",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "ALL",
        "name_plural": "Albanian lekë"
    },
    "AMD": {
        "symbol": "AMD",
        "name": "Armenian Dram",
        "symbol_native": "դր.",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "AMD",
        "name_plural": "Armenian drams"
    },
    "ARS": {
        "symbol": "AR$",
        "name": "Argentine Peso",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "ARS",
        "name_plural": "Argentine pesos"
    },
    "AUD": {
        "symbol": "AU$",
        "name": "Australian Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "AUD",
        "name_plural": "Australian dollars"
    },
    "AZN": {
        "symbol": "man.",
        "name": "Azerbaijani Manat",
        "symbol_native": "ман.",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "AZN",
        "name_plural": "Azerbaijani manats"
    },
    "BAM": {
        "symbol": "KM",
        "name": "Bosnia-Herzegovina Convertible Mark",
        "symbol_native": "KM",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "BAM",
        "name_plural": "Bosnia-Herzegovina convertible marks"
    },
    "BDT": {
        "symbol": "Tk",
        "name": "Bangladeshi Taka",
        "symbol_native": "৳",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "BDT",
        "name_plural": "Bangladeshi takas"
    },
    "BGN": {
        "symbol": "BGN",
        "name": "Bulgarian Lev",
        "symbol_native": "лв.",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "BGN",
        "name_plural": "Bulgarian leva"
    },
    "BHD": {
        "symbol": "BD",
        "name": "Bahraini Dinar",
        "symbol_native": "د.ب.‏",
        "decimal_digits": 3,
        "rounding": 0,
        "code": "BHD",
        "name_plural": "Bahraini dinars"
    },
    "BIF": {
        "symbol": "FBu",
        "name": "Burundian Franc",
        "symbol_native": "FBu",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "BIF",
        "name_plural": "Burundian francs"
    },
    "BND": {
        "symbol": "BN$",
        "name": "Brunei Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "BND",
        "name_plural": "Brunei dollars"
    },
    "BOB": {
        "symbol": "Bs",
        "name": "Bolivian Boliviano",
        "symbol_native": "Bs",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "BOB",
        "name_plural": "Bolivian bolivianos"
    },
    "BRL": {
        "symbol": "R$",
        "name": "Brazilian Real",
        "symbol_native": "R$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "BRL",
        "name_plural": "Brazilian reals"
    },
    "BWP": {
        "symbol": "BWP",
        "name": "Botswanan Pula",
        "symbol_native": "P",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "BWP",
        "name_plural": "Botswanan pulas"
    },
    "BYR": {
        "symbol": "BYR",
        "name": "Belarusian Ruble",
        "symbol_native": "BYR",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "BYR",
        "name_plural": "Belarusian rubles"
    },
    "BZD": {
        "symbol": "BZ$",
        "name": "Belize Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "BZD",
        "name_plural": "Belize dollars"
    },
    "CDF": {
        "symbol": "CDF",
        "name": "Congolese Franc",
        "symbol_native": "FrCD",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "CDF",
        "name_plural": "Congolese francs"
    },
    "CHF": {
        "symbol": "CHF",
        "name": "Swiss Franc",
        "symbol_native": "CHF",
        "decimal_digits": 2,
        "rounding": 0.05,
        "code": "CHF",
        "name_plural": "Swiss francs"
    },
    "CLP": {
        "symbol": "CL$",
        "name": "Chilean Peso",
        "symbol_native": "$",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "CLP",
        "name_plural": "Chilean pesos"
    },
    "CNY": {
        "symbol": "CN¥",
        "name": "Chinese Yuan",
        "symbol_native": "CN¥",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "CNY",
        "name_plural": "Chinese yuan"
    },
    "COP": {
        "symbol": "CO$",
        "name": "Colombian Peso",
        "symbol_native": "$",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "COP",
        "name_plural": "Colombian pesos"
    },
    "CRC": {
        "symbol": "₡",
        "name": "Costa Rican Colón",
        "symbol_native": "₡",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "CRC",
        "name_plural": "Costa Rican colóns"
    },
    "CVE": {
        "symbol": "CV$",
        "name": "Cape Verdean Escudo",
        "symbol_native": "CV$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "CVE",
        "name_plural": "Cape Verdean escudos"
    },
    "CZK": {
        "symbol": "Kč",
        "name": "Czech Republic Koruna",
        "symbol_native": "Kč",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "CZK",
        "name_plural": "Czech Republic korunas"
    },
    "DJF": {
        "symbol": "Fdj",
        "name": "Djiboutian Franc",
        "symbol_native": "Fdj",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "DJF",
        "name_plural": "Djiboutian francs"
    },
    "DKK": {
        "symbol": "Dkr",
        "name": "Danish Krone",
        "symbol_native": "kr",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "DKK",
        "name_plural": "Danish kroner"
    },
    "DOP": {
        "symbol": "RD$",
        "name": "Dominican Peso",
        "symbol_native": "RD$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "DOP",
        "name_plural": "Dominican pesos"
    },
    "DZD": {
        "symbol": "DA",
        "name": "Algerian Dinar",
        "symbol_native": "د.ج.‏",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "DZD",
        "name_plural": "Algerian dinars"
    },
    "EEK": {
        "symbol": "Ekr",
        "name": "Estonian Kroon",
        "symbol_native": "kr",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "EEK",
        "name_plural": "Estonian kroons"
    },
    "EGP": {
        "symbol": "EGP",
        "name": "Egyptian Pound",
        "symbol_native": "ج.م.‏",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "EGP",
        "name_plural": "Egyptian pounds"
    },
    "ERN": {
        "symbol": "Nfk",
        "name": "Eritrean Nakfa",
        "symbol_native": "Nfk",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "ERN",
        "name_plural": "Eritrean nakfas"
    },
    "ETB": {
        "symbol": "Br",
        "name": "Ethiopian Birr",
        "symbol_native": "Br",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "ETB",
        "name_plural": "Ethiopian birrs"
    },
    "GBP": {
        "symbol": "£",
        "name": "British Pound Sterling",
        "symbol_native": "£",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "GBP",
        "name_plural": "British pounds sterling"
    },
    "GEL": {
        "symbol": "GEL",
        "name": "Georgian Lari",
        "symbol_native": "GEL",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "GEL",
        "name_plural": "Georgian laris"
    },
    "GHS": {
        "symbol": "GH₵",
        "name": "Ghanaian Cedi",
        "symbol_native": "GH₵",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "GHS",
        "name_plural": "Ghanaian cedis"
    },
    "GNF": {
        "symbol": "FG",
        "name": "Guinean Franc",
        "symbol_native": "FG",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "GNF",
        "name_plural": "Guinean francs"
    },
    "GTQ": {
        "symbol": "GTQ",
        "name": "Guatemalan Quetzal",
        "symbol_native": "Q",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "GTQ",
        "name_plural": "Guatemalan quetzals"
    },
    "HKD": {
        "symbol": "HK$",
        "name": "Hong Kong Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "HKD",
        "name_plural": "Hong Kong dollars"
    },
    "HNL": {
        "symbol": "HNL",
        "name": "Honduran Lempira",
        "symbol_native": "L",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "HNL",
        "name_plural": "Honduran lempiras"
    },
    "HRK": {
        "symbol": "kn",
        "name": "Croatian Kuna",
        "symbol_native": "kn",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "HRK",
        "name_plural": "Croatian kunas"
    },
    "HUF": {
        "symbol": "Ft",
        "name": "Hungarian Forint",
        "symbol_native": "Ft",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "HUF",
        "name_plural": "Hungarian forints"
    },
    "IDR": {
        "symbol": "Rp",
        "name": "Indonesian Rupiah",
        "symbol_native": "Rp",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "IDR",
        "name_plural": "Indonesian rupiahs"
    },
    "ILS": {
        "symbol": "₪",
        "name": "Israeli New Sheqel",
        "symbol_native": "₪",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "ILS",
        "name_plural": "Israeli new sheqels"
    },
    "INR": {
        "symbol": "Rs",
        "name": "Indian Rupee",
        "symbol_native": "টকা",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "INR",
        "name_plural": "Indian rupees"
    },
    "IQD": {
        "symbol": "IQD",
        "name": "Iraqi Dinar",
        "symbol_native": "د.ع.‏",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "IQD",
        "name_plural": "Iraqi dinars"
    },
    "IRR": {
        "symbol": "IRR",
        "name": "Iranian Rial",
        "symbol_native": "﷼",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "IRR",
        "name_plural": "Iranian rials"
    },
    "ISK": {
        "symbol": "Ikr",
        "name": "Icelandic Króna",
        "symbol_native": "kr",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "ISK",
        "name_plural": "Icelandic krónur"
    },
    "JMD": {
        "symbol": "J$",
        "name": "Jamaican Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "JMD",
        "name_plural": "Jamaican dollars"
    },
    "JOD": {
        "symbol": "JD",
        "name": "Jordanian Dinar",
        "symbol_native": "د.أ.‏",
        "decimal_digits": 3,
        "rounding": 0,
        "code": "JOD",
        "name_plural": "Jordanian dinars"
    },
    "JPY": {
        "symbol": "¥",
        "name": "Japanese Yen",
        "symbol_native": "¥",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "JPY",
        "name_plural": "Japanese yen"
    },
    "KES": {
        "symbol": "Ksh",
        "name": "Kenyan Shilling",
        "symbol_native": "Ksh",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "KES",
        "name_plural": "Kenyan shillings"
    },
    "KHR": {
        "symbol": "KHR",
        "name": "Cambodian Riel",
        "symbol_native": "៛",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "KHR",
        "name_plural": "Cambodian riels"
    },
    "KMF": {
        "symbol": "CF",
        "name": "Comorian Franc",
        "symbol_native": "FC",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "KMF",
        "name_plural": "Comorian francs"
    },
    "KRW": {
        "symbol": "₩",
        "name": "South Korean Won",
        "symbol_native": "₩",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "KRW",
        "name_plural": "South Korean won"
    },
    "KWD": {
        "symbol": "KD",
        "name": "Kuwaiti Dinar",
        "symbol_native": "د.ك.‏",
        "decimal_digits": 3,
        "rounding": 0,
        "code": "KWD",
        "name_plural": "Kuwaiti dinars"
    },
    "KZT": {
        "symbol": "KZT",
        "name": "Kazakhstani Tenge",
        "symbol_native": "тңг.",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "KZT",
        "name_plural": "Kazakhstani tenges"
    },
    "LBP": {
        "symbol": "LB£",
        "name": "Lebanese Pound",
        "symbol_native": "ل.ل.‏",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "LBP",
        "name_plural": "Lebanese pounds"
    },
    "LKR": {
        "symbol": "SLRs",
        "name": "Sri Lankan Rupee",
        "symbol_native": "SL Re",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "LKR",
        "name_plural": "Sri Lankan rupees"
    },
    "LTL": {
        "symbol": "Lt",
        "name": "Lithuanian Litas",
        "symbol_native": "Lt",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "LTL",
        "name_plural": "Lithuanian litai"
    },
    "LVL": {
        "symbol": "Ls",
        "name": "Latvian Lats",
        "symbol_native": "Ls",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "LVL",
        "name_plural": "Latvian lati"
    },
    "LYD": {
        "symbol": "LD",
        "name": "Libyan Dinar",
        "symbol_native": "د.ل.‏",
        "decimal_digits": 3,
        "rounding": 0,
        "code": "LYD",
        "name_plural": "Libyan dinars"
    },
    "MAD": {
        "symbol": "MAD",
        "name": "Moroccan Dirham",
        "symbol_native": "د.م.‏",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "MAD",
        "name_plural": "Moroccan dirhams"
    },
    "MDL": {
        "symbol": "MDL",
        "name": "Moldovan Leu",
        "symbol_native": "MDL",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "MDL",
        "name_plural": "Moldovan lei"
    },
    "MGA": {
        "symbol": "MGA",
        "name": "Malagasy Ariary",
        "symbol_native": "MGA",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "MGA",
        "name_plural": "Malagasy Ariaries"
    },
    "MKD": {
        "symbol": "MKD",
        "name": "Macedonian Denar",
        "symbol_native": "MKD",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "MKD",
        "name_plural": "Macedonian denari"
    },
    "MMK": {
        "symbol": "MMK",
        "name": "Myanma Kyat",
        "symbol_native": "K",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "MMK",
        "name_plural": "Myanma kyats"
    },
    "MOP": {
        "symbol": "MOP$",
        "name": "Macanese Pataca",
        "symbol_native": "MOP$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "MOP",
        "name_plural": "Macanese patacas"
    },
    "MUR": {
        "symbol": "MURs",
        "name": "Mauritian Rupee",
        "symbol_native": "MURs",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "MUR",
        "name_plural": "Mauritian rupees"
    },
    "MXN": {
        "symbol": "MX$",
        "name": "Mexican Peso",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "MXN",
        "name_plural": "Mexican pesos"
    },
    "MYR": {
        "symbol": "RM",
        "name": "Malaysian Ringgit",
        "symbol_native": "RM",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "MYR",
        "name_plural": "Malaysian ringgits"
    },
    "MZN": {
        "symbol": "MTn",
        "name": "Mozambican Metical",
        "symbol_native": "MTn",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "MZN",
        "name_plural": "Mozambican meticals"
    },
    "NAD": {
        "symbol": "N$",
        "name": "Namibian Dollar",
        "symbol_native": "N$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "NAD",
        "name_plural": "Namibian dollars"
    },
    "NGN": {
        "symbol": "₦",
        "name": "Nigerian Naira",
        "symbol_native": "₦",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "NGN",
        "name_plural": "Nigerian nairas"
    },
    "NIO": {
        "symbol": "C$",
        "name": "Nicaraguan Córdoba",
        "symbol_native": "C$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "NIO",
        "name_plural": "Nicaraguan córdobas"
    },
    "NOK": {
        "symbol": "Nkr",
        "name": "Norwegian Krone",
        "symbol_native": "kr",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "NOK",
        "name_plural": "Norwegian kroner"
    },
    "NPR": {
        "symbol": "NPRs",
        "name": "Nepalese Rupee",
        "symbol_native": "नेरू",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "NPR",
        "name_plural": "Nepalese rupees"
    },
    "NZD": {
        "symbol": "NZ$",
        "name": "New Zealand Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "NZD",
        "name_plural": "New Zealand dollars"
    },
    "OMR": {
        "symbol": "OMR",
        "name": "Omani Rial",
        "symbol_native": "ر.ع.‏",
        "decimal_digits": 3,
        "rounding": 0,
        "code": "OMR",
        "name_plural": "Omani rials"
    },
    "PAB": {
        "symbol": "B/.",
        "name": "Panamanian Balboa",
        "symbol_native": "B/.",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "PAB",
        "name_plural": "Panamanian balboas"
    },
    "PEN": {
        "symbol": "S/.",
        "name": "Peruvian Nuevo Sol",
        "symbol_native": "S/.",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "PEN",
        "name_plural": "Peruvian nuevos soles"
    },
    "PHP": {
        "symbol": "₱",
        "name": "Philippine Peso",
        "symbol_native": "₱",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "PHP",
        "name_plural": "Philippine pesos"
    },
    "PKR": {
        "symbol": "PKRs",
        "name": "Pakistani Rupee",
        "symbol_native": "₨",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "PKR",
        "name_plural": "Pakistani rupees"
    },
    "PLN": {
        "symbol": "zł",
        "name": "Polish Zloty",
        "symbol_native": "zł",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "PLN",
        "name_plural": "Polish zlotys"
    },
    "PYG": {
        "symbol": "₲",
        "name": "Paraguayan Guarani",
        "symbol_native": "₲",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "PYG",
        "name_plural": "Paraguayan guaranis"
    },
    "QAR": {
        "symbol": "QR",
        "name": "Qatari Rial",
        "symbol_native": "ر.ق.‏",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "QAR",
        "name_plural": "Qatari rials"
    },
    "RON": {
        "symbol": "RON",
        "name": "Romanian Leu",
        "symbol_native": "RON",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "RON",
        "name_plural": "Romanian lei"
    },
    "RSD": {
        "symbol": "din.",
        "name": "Serbian Dinar",
        "symbol_native": "дин.",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "RSD",
        "name_plural": "Serbian dinars"
    },
    "RUB": {
        "symbol": "RUB",
        "name": "Russian Ruble",
        "symbol_native": "руб.",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "RUB",
        "name_plural": "Russian rubles"
    },
    "RWF": {
        "symbol": "RWF",
        "name": "Rwandan Franc",
        "symbol_native": "FR",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "RWF",
        "name_plural": "Rwandan francs"
    },
    "SAR": {
        "symbol": "SR",
        "name": "Saudi Riyal",
        "symbol_native": "ر.س.‏",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "SAR",
        "name_plural": "Saudi riyals"
    },
    "SDG": {
        "symbol": "SDG",
        "name": "Sudanese Pound",
        "symbol_native": "SDG",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "SDG",
        "name_plural": "Sudanese pounds"
    },
    "SEK": {
        "symbol": "Skr",
        "name": "Swedish Krona",
        "symbol_native": "kr",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "SEK",
        "name_plural": "Swedish kronor"
    },
    "SGD": {
        "symbol": "S$",
        "name": "Singapore Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "SGD",
        "name_plural": "Singapore dollars"
    },
    "SOS": {
        "symbol": "Ssh",
        "name": "Somali Shilling",
        "symbol_native": "Ssh",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "SOS",
        "name_plural": "Somali shillings"
    },
    "SYP": {
        "symbol": "SY£",
        "name": "Syrian Pound",
        "symbol_native": "ل.س.‏",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "SYP",
        "name_plural": "Syrian pounds"
    },
    "THB": {
        "symbol": "฿",
        "name": "Thai Baht",
        "symbol_native": "฿",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "THB",
        "name_plural": "Thai baht"
    },
    "TND": {
        "symbol": "DT",
        "name": "Tunisian Dinar",
        "symbol_native": "د.ت.‏",
        "decimal_digits": 3,
        "rounding": 0,
        "code": "TND",
        "name_plural": "Tunisian dinars"
    },
    "TOP": {
        "symbol": "T$",
        "name": "Tongan Paʻanga",
        "symbol_native": "T$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "TOP",
        "name_plural": "Tongan paʻanga"
    },
    "TRY": {
        "symbol": "TL",
        "name": "Turkish Lira",
        "symbol_native": "TL",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "TRY",
        "name_plural": "Turkish Lira"
    },
    "TTD": {
        "symbol": "TT$",
        "name": "Trinidad and Tobago Dollar",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "TTD",
        "name_plural": "Trinidad and Tobago dollars"
    },
    "TWD": {
        "symbol": "NT$",
        "name": "New Taiwan Dollar",
        "symbol_native": "NT$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "TWD",
        "name_plural": "New Taiwan dollars"
    },
    "TZS": {
        "symbol": "TSh",
        "name": "Tanzanian Shilling",
        "symbol_native": "TSh",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "TZS",
        "name_plural": "Tanzanian shillings"
    },
    "UAH": {
        "symbol": "₴",
        "name": "Ukrainian Hryvnia",
        "symbol_native": "₴",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "UAH",
        "name_plural": "Ukrainian hryvnias"
    },
    "UGX": {
        "symbol": "USh",
        "name": "Ugandan Shilling",
        "symbol_native": "USh",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "UGX",
        "name_plural": "Ugandan shillings"
    },
    "UYU": {
        "symbol": "$U",
        "name": "Uruguayan Peso",
        "symbol_native": "$",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "UYU",
        "name_plural": "Uruguayan pesos"
    },
    "UZS": {
        "symbol": "UZS",
        "name": "Uzbekistan Som",
        "symbol_native": "UZS",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "UZS",
        "name_plural": "Uzbekistan som"
    },
    "VEF": {
        "symbol": "Bs.F.",
        "name": "Venezuelan Bolívar",
        "symbol_native": "Bs.F.",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "VEF",
        "name_plural": "Venezuelan bolívars"
    },
    "VND": {
        "symbol": "₫",
        "name": "Vietnamese Dong",
        "symbol_native": "₫",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "VND",
        "name_plural": "Vietnamese dong"
    },
    "XAF": {
        "symbol": "FCFA",
        "name": "CFA Franc BEAC",
        "symbol_native": "FCFA",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "XAF",
        "name_plural": "CFA francs BEAC"
    },
    "XOF": {
        "symbol": "CFA",
        "name": "CFA Franc BCEAO",
        "symbol_native": "CFA",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "XOF",
        "name_plural": "CFA francs BCEAO"
    },
    "YER": {
        "symbol": "YR",
        "name": "Yemeni Rial",
        "symbol_native": "ر.ي.‏",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "YER",
        "name_plural": "Yemeni rials"
    },
    "ZAR": {
        "symbol": "R",
        "name": "South African Rand",
        "symbol_native": "R",
        "decimal_digits": 2,
        "rounding": 0,
        "code": "ZAR",
        "name_plural": "South African rand"
    },
    "ZMK": {
        "symbol": "ZK",
        "name": "Zambian Kwacha",
        "symbol_native": "ZK",
        "decimal_digits": 0,
        "rounding": 0,
        "code": "ZMK",
        "name_plural": "Zambian kwachas"
    }
}

We will use this in the currency conversion page.

In index.html, we add:

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,400italic|Material+Icons">

This displays Roboto so our app looks good with the Material widgets.

Then in src/router/index.js, we add:

import Vue from 'vue'
import Router from 'vue-router'
import VeeValidate from 'vee-validate';
import Quotes from '@/components/Quotes'
import Convert from '@/components/Convert'
import Drawer from '@/components/Drawer'
import Historical from '@/components/Historical'
import HistoricalRatesInPeriod from '@/components/HistoricalRatesInPeriod'
import HistoricalRatesInPeriodGraph from '@/components/HistoricalRatesInPeriodGraph'
import LineGraph from '@/components/LineGraph'
import currencies from '../currencies';
import VueMaterial from 'vue-material'
import 'vue-material/dist/vue-material.min.css'
import 'vue-material/dist/theme/default.css'Vue.component('drawer', Drawer);
Vue.component('line-graph', LineGraph);
Vue.use(VueMaterial)
Vue.use(Router)
Vue.use(VeeValidate);Vue.filter('currencyName', function (value) {
  if (currencies[value]) {
    return currencies[value].name;
  }
  else {
    return value;
  }})export default new Router({
  routes: [
    {
      path: '/',
      name: 'quotes',
      component: Quotes,
      meta: { title: 'Quotes' }
    },
    {
      path: '/convert',
      name: 'convert',
      component: Convert,
      meta: { title: 'Convert' }
    },
    {
      path: '/historical',
      name: 'historical',
      component: Historical,
      meta: { title: 'Historical Rates' }
    },
    {
      path: '/historicalratesinperiod',
      name: 'historicalratesinperiod',
      component: HistoricalRatesInPeriod,
      meta: { title: 'Historical Rates in a Period' }
    },
    {
      path: '/historicalratesinperiodgraph',
      name: 'historicalratesinperiodgraph',
      component: HistoricalRatesInPeriodGraph,
      meta: { title: 'Historical Rates in a Period' }
    }
  ]
})

This block is where we register our external components with Vue so we can use it in our templates:

Vue.component('drawer', Drawer);
Vue.component('line-graph', LineGraph);
Vue.use(VueMaterial)
Vue.use(Router)
Vue.use(VeeValidate);

Vue also has an element called filter that allows us to map one value to another in our template.

This block gets the currency code and gets the name:

Vue.filter('currencyName', function (value) {
  if (currencies[value]) {
    return currencies[value].name;
  }
  else {
    return value;
  }})

These are the routes for each page of our app. The titles of each page are in the title property of the meta object in each route. The path is the relative URL of each page. We will have to add the components we imported in the file above.

Next, we create the page for the currency-conversion form. We add a file called Convert.vue in thesrc/components folder. The vue extension indicates it is a single-file component, which means that the logic, the style, and the template of the component are all included in one file.

The Convert.vue code looks like this:

<template>
  <div class="page-container md-layout-column">
    <drawer></drawer>
    <div class="container">
      <h1 class="center">Convert Currency</h1>
      <form @submit.prevent="convert">
        <div class="md-layout-item">
          <md-field :class="{'md-invalid': errors.first('price') }">
            <label>Amount of Currency to Convert From</label>
            <md-input v-model="form.price" v-validate="'required'" name="price"></md-input>
            <span class="md-error">{{ errors.first('price') }}</span>
          </md-field>
          <md-field :class="{'md-invalid': errors.first('from') }">
            <label for="from">Currency to Convert From</label>
            <md-select v-model="form.from" name="from" v-validate="'required'">
              <md-option :value="s.code" v-for="s in fromSymbols" :key="s.code">{{s.name}}</md-option>
            </md-select>
            <span class="md-error">{{ errors.first('from') }}</span>
          </md-field>
          <md-field :class="{'md-invalid': errors.first('to') }">
            <label for="to">Currency to Convert To</label>
            <md-select v-model="form.to" name="to" v-validate="'required'">
              <md-option :value="s.code" v-for="s in toSymbols" :key="s.code">{{s.name}}</md-option>
            </md-select>
            <span class="md-error">{{ errors.first('to') }}</span>
          </md-field>
        </div>
        <md-button class="md-dense md-raised md-primary" type="submit">Convert</md-button>
      </form>
      <div v-if="hasResult" class="center">
        <h2
          v-if="isNumber(result)"
        >{{form.price | currencyName}} {{form.from}} is {{result}} {{form.to | currencyName}}</h2>
        <h2 v-if="!isNumber(result)">Exchange rate not found.</h2>
      </div>
    </div>
  </div>
</template><script>
import currencies from "../currencies";
import { APIURL } from "../urls";
import * as moment from "moment";
const request = require("superagent");export default {
  name: "convert",
  data() {
    return {
      form: {
        price: 0,
        from: "",
        to: ""
      },
      result: null,
      currencies,
      currentTime: "",
      err: null,
      hasResult: false,
      symbols: [],
      fromSymbols: [],
      toSymbols: []
    };
  },
  watch: {
    form: {
      handler(val) {
        if (!this.form) {
          this.toSymbols = this.symbols;
          this.fromSymbols = this.symbols;
          return;
        }this.hasResult = false;
        if (this.form.from) {
          this.toSymbols = this.symbols.filter(s => s.code != this.form.from);
        }if (this.form.to) {
          this.fromSymbols = this.symbols.filter(s => s.code != this.form.to);
        }
      },
      deep: true
    }
  },
  methods: {
    convert(evt) {
      this.hasResult = false;
      evt.preventDefault();
      if (this.errors.items.length > 0) {
        return;
      }request.get(`${APIURL}/latest?base=${this.form.from}`).end((err, res) => {
        let body = res.body;
        this.hasResult = true;
        this.result = (+this.form.price * +body.rates[this.form.to]).toFixed(2);
        this.currentTime = moment(this.result.timestamp * 1000).format(
          "YYYY-MM-DD HH:mm:ss a"
        );
      });
    },
    isNumber(number) {
      return !isNaN(number);
    }
  },
  beforeMount() {
    this.symbols = Object.keys(this.currencies).map(k => {
      return {
        symbol: k,
        ...this.currencies[k]
      };
    });
    this.toSymbols = JSON.parse(JSON.stringify(this.symbols));
    this.fromSymbols = JSON.parse(JSON.stringify(this.symbols));
  }
};
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.result {
  margin-top: 30px;
}
</style>

Note that it uses the currencyName filter that we defined earlier.

The part between the template tags is the form. There is an input and two select dropdowns for the currency we are converting from and to respectively.

In each form field, we check if it has been filled with the v-validate="'required'" attribute. The options are imported from the JSON file with all the currencies. We remove the currency that has already been selected in one of the dropdowns in the watch block in the object that we are exporting in the script tags:

watch: {
    form: {
      handler(val) {
        if (!this.form) {
          this.toSymbols = this.symbols;
          this.fromSymbols = this.symbols;
          return;
        }this.hasResult = false;
        if (this.form.from) {
          this.toSymbols = this.symbols.filter(s => s.code != this.form.from);
        }if (this.form.to) {
          this.fromSymbols = this.symbols.filter(s => s.code != this.form.to);
        }
      },
      deep: true
    }
  },

This makes it so we don’t have the same selection in both dropdowns.

In the code above, we’re watching for changes to all properties of the form object. If the new currency symbol is selected in either the to or from dropdown, then that symbol will be excluded from selection in the to or from field, respectively.

Once the user clicks the submit button, the convert function runs, which gets the exchange rate and computes the converted amount. The this in the object is where the template variables are bound to the variables in the logic. We use moment to format the time, so we have to install that by running npm i moment.

We display the result by assigning to the fields of this. The templates will automatically update.

To create a side menu for navigation, we add a file called Drawer.vue, which we import and register it in index.js so we can include it our template.

The code looks like this:

<template>
  <div>
    <md-drawer :md-active.sync="showNavigation" md-swipeable>
      <md-toolbar class="md-transparent" md-elevation="0">
        <span class="md-title">Currency Info</span>
      </md-toolbar><md-list>
        <md-list-item>
          <router-link to="/">
            <span class="md-list-item-text">Exchange Rates</span>
          </router-link>
        </md-list-item><md-list-item>
          <router-link to="/convert">
            <span class="md-list-item-text">Convert Currency</span>
          </router-link>
        </md-list-item><md-list-item>
          <router-link to="/historical">
            <span class="md-list-item-text">Historical Rates</span>
          </router-link>
        </md-list-item><md-list-item>
          <router-link to="/historicalratesinperiod">
            <span class="md-list-item-text">Historical Rates in a Period</span>
          </router-link>
        </md-list-item><md-list-item>
          <router-link to="/historicalratesinperiodgraph">
            <span class="md-list-item-text">Historical Rates in a Period Graph</span>
          </router-link>
        </md-list-item>
      </md-list>
    </md-drawer>
    <md-toolbar class="md-primary">
      <md-button class="md-icon-button" @click="showNavigation = true">
        <md-icon>menu</md-icon>
      </md-button>
      <h3 class="md-title">Currency Info</h3>
    </md-toolbar>
  </div>
</template><script>
export default {
  name: "nav-bar",
  data() {
    return {
      msg: "Welcome to Your Vue.js App",
      showNavigation: false
    };
  }
};
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

Now we create the rest of the component files in a similar fashion. They are all in src/components.

We create a file called Historical.vue and add the following:

<template>
  <div class="page-container md-layout-column">
    <drawer></drawer>
    <div class="container">
      <h1 class="center">Historical Exchange Rates</h1>
      <form @submit.prevent="getHistoricalRate">
        <div class="md-layout-item">
          <label>Date</label>
          <md-datepicker
            v-model="form.date"
            v-validate="'required'"
            name="date"
            :md-disabled-dates="futureDates"
          ></md-datepicker>
          <md-field :class="{'md-invalid': errors.first('baseCurrency') }">
            <label for="baseCurrency">Currency Symbol</label>
            <md-select v-model="form.baseCurrency" name="baseCurrency" v-validate="'required'">
              <md-option :value="s.code" v-for="s in symbols" :key="s.code">{{s.name}}</md-option>
            </md-select>
            <span class="md-error">{{ errors.first('baseCurrency') }}</span>
          </md-field>
        </div>
        <md-button class="md-dense md-raised md-primary" type="submit">Find Rate</md-button>
      </form>
      <div>
        <md-content class="md-layout-item" v-for="(r, index) in rates" :key="index">
          <h2>{{form.baseCurrency | currencyName}} - {{r.symbol| currencyName}}</h2>
          <p>{{r.rate}}</p>
        </md-content>
      </div>
    </div>
  </div>
</template><script>
import currencies from "../currencies";
import { APIURL } from "../urls";
import * as moment from "moment";
const request = require("superagent");export default {
  data() {
    return {
      form: {
        date: new Date(),
        baseCurrency: ""
      },
      rates: [],
      symbols: [],
      currencies
    };
  },
  methods: {
    getHistoricalRate(evt) {
      this.hasResult = false;
      evt.preventDefault();
      if (this.errors.items.length > 0) {
        return;
      }request
        .get(
          `${APIURL}/${moment(this.form.date).format("YYYY-MM-DD")}?base=${
            this.form.baseCurrency
          }`
        )
        .end((err, res) => {
          let body = res.body;
          this.hasResult = true;
          this.rates = Object.keys(body.rates).map(k => {
            return {
              symbol: k,
              rate: body.rates[k]
            };
          });
        });
    },
    futureDates(evt) {
      return +evt > +new Date();
    }
  },
  beforeMount() {
    this.symbols = Object.keys(this.currencies).map(k => {
      return {
        symbol: k,
        ...this.currencies[k]
      };
    });
  }
};
</script><style>
</style>

In the component, thegetHistoricalRate function runs code to make a GET request to the API to exchange with our Superagent HTTP client library. After the request is done, the then function that is chained to the getfunction takes the exchange rate result and does calculations.

Then we create HistoricalRatesInPeriod.vue in the same folder and add the following:

<template>
  <div class="page-container md-layout-column">
    <drawer></drawer>
    <div class="container">
      <h1 class="center">Historical Exchange Rates in a Period</h1>
      <form @submit.prevent="getHistoricalRate">
        <div class="md-layout-item">
          <label>Start Date</label>
          <md-datepicker
            v-model="form.startDate"
            v-validate="'required'"
            name="date"
            :md-disabled-dates="invalidStartDates"
          ></md-datepicker>
          <label>End Date</label>
          <md-datepicker
            v-model="form.endDate"
            v-validate="'required'"
            name="date"
            :md-disabled-dates="invalidEndDates"
          ></md-datepicker>
          <md-field :class="{'md-invalid': errors.first('baseCurrency') }">
            <label for="baseCurrency">Currency Symbol</label>
            <md-select v-model="form.baseCurrency" name="baseCurrency" v-validate="'required'">
              <md-option :value="s.code" v-for="s in symbols" :key="s.code">{{s.name}}</md-option>
            </md-select>
            <span class="md-error">{{ errors.first('baseCurrency') }}</span>
          </md-field>
        </div>
        <md-button class="md-dense md-raised md-primary" type="submit">Find Rate</md-button>
      </form>
      <div>
        <md-content class="md-layout-item" v-for="(r, index) in rates" :key="index">
          <h2>{{r.date}}</h2>
          <md-content class="md-layout-item" v-for="(rr, index) in r.rates" :key="index">
            <h3>{{form.baseCurrency | currencyName}} - {{rr.symbol| currencyName}}</h3>
            <p>{{rr.rate}}</p>
          </md-content>
        </md-content>
      </div>
    </div>
  </div>
</template><script>
import currencies from "../currencies";
import { APIURL } from "../urls";
import * as moment from "moment";
const request = require("superagent");export default {
  data() {
    return {
      form: {
        startDate: new Date(),
        endDate: new Date(),
        baseCurrency: ""
      },
      rates: [],
      symbols: [],
      currencies
    };
  },
  methods: {
    getHistoricalRate(evt) {
      this.hasResult = false;
      evt.preventDefault();
      if (this.errors.items.length > 0) {
        return;
      }request
        .get(
          `${APIURL}/history?start_at=${moment(this.form.startDate).format(
            "YYYY-MM-DD"
          )}&end_at=${moment(this.form.endDate).format("YYYY-MM-DD")}&base=${
            this.form.baseCurrency
          }`
        )
        .end((err, res) => {
          let body = res.body;
          this.hasResult = true;
          this.rates = Object.keys(body.rates)
            .map(date => {
              return {
                date: moment(date).format("YYYY-MM-DD"),
                rates: Object.keys(body.rates[date]).map(k => {
                  return {
                    symbol: k,
                    rate: body.rates[date][k]
                  };
                })
              };
            })
            .sort((a, b) => +new Date(a.date) - +new Date(b.date));
        });
    },
    invalidStartDates(evt) {
      return +evt > +new Date();
    },
    invalidEndDates(evt) {
      return +evt < +this.form.startDate || +evt > +new Date();
    }
  },
  beforeMount() {
    this.symbols = Object.keys(this.currencies).map(k => {
      return {
        symbol: k,
        ...this.currencies[k]
      };
    });
  }
};
</script><style>
</style>

The date picker and other components are all registered in index.js so we can include them in our templates.

Next, we add a page to “Historical Exchange Rates in a Period” in a graph by adding a file called HistoreicalRatesInPeriodGraph.vue and add the following code:

<template>
  <div class="page-container md-layout-column">
    <drawer></drawer>
    <div class="container">
      <h1 class="center">Historical Exchange Rates in a Period Graph</h1>
      <form @submit.prevent="getHistoricalRate">
        <div class="md-layout-item">
          <label>Start Date</label>
          <md-datepicker
            v-model="form.startDate"
            v-validate="'required'"
            name="date"
            :md-disabled-dates="invalidStartDates"
          ></md-datepicker>
          <label>End Date</label>
          <md-datepicker
            v-model="form.endDate"
            v-validate="'required'"
            name="date"
            :md-disabled-dates="invalidEndDates"
          ></md-datepicker>
          <md-field :class="{'md-invalid': errors.first('from') }">
            <label for="from">Currency to Convert From</label>
            <md-select v-model="form.from" name="from" v-validate="'required'">
              <md-option :value="s.code" v-for="s in fromSymbols" :key="s.code">{{s.name}}</md-option>
            </md-select>
            <span class="md-error">{{ errors.first('from') }}</span>
          </md-field>
          <md-field :class="{'md-invalid': errors.first('to') }">
            <label for="to">Currency to Convert To</label>
            <md-select v-model="form.to" name="to" v-validate="'required'">
              <md-option :value="s.code" v-for="s in toSymbols" :key="s.code">{{s.name}}</md-option>
            </md-select>
            <span class="md-error">{{ errors.first('to') }}</span>
          </md-field>
        </div>
        <md-button class="md-dense md-raised md-primary" type="submit">Find Historical Rates</md-button>
      </form>
      <div>
        <line-graph v-if="hasResult && !resultNotFound" :chartdata="graphData" :options="options"></line-graph>
        <h2 v-if="hasResult && resultNotFound">Exchange rates not found.</h2>
      </div>
    </div>
  </div>
</template><script>
import currencies from "../currencies";
import { APIURL } from "../urls";
import * as moment from "moment";
const request = require("superagent");export default {
  data() {
    return {
      form: {
        startDate: new Date(),
        endDate: new Date(),
        from: "",
        to: ""
      },
      rates: [],
      symbols: [],
      fromSymbols: [],
      toSymbols: [],
      currencies,
      graphData: {},
      options: {
        responsive: true,
        maintainAspectRatio: false
      },
      hasResult: false,
      resultNotFound: false
    };
  },
  watch: {
    form: {
      handler(val) {
        if (!this.form) {
          this.toSymbols = this.symbols;
          this.fromSymbols = this.symbols;
          return;
        }

        if (this.form.from) {
          this.toSymbols = this.symbols.filter(s => s.code != this.form.from);
        }if (this.form.to) {
          this.fromSymbols = this.symbols.filter(s => s.code != this.form.to);
        }
      },
      deep: true
    }
  },
  methods: {
    getHistoricalRate(evt) {
      this.hasResult = false;
      evt.preventDefault();
      if (this.errors.items.length > 0) {
        return;
      }request
        .get(
          `${APIURL}/history?start_at=${moment(this.form.startDate).format(
            "YYYY-MM-DD"
          )}&end_at=${moment(this.form.endDate).format("YYYY-MM-DD")}&base=${
            this.form.from
          }`
        )
        .end((err, res) => {
          let body = res.body;
          this.hasResult = true;
          if (!body.rates) {
            this.resultNotFound = true;
            return;
          } else {
            this.resultNotFound = false;
          }
          let rates = Object.keys(body.rates)
            .map(date => {
              return {
                date: moment(date).format("YYYY-MM-DD"),
                rates: Object.keys(body.rates[date]).map(k => {
                  return {
                    symbol: k,
                    rate: body.rates[date][k]
                  };
                })
              };
            })
            .sort((a, b) => +new Date(a.date) - +new Date(b.date));let exchangeRates = [];
          rates.forEach(r => {
            let currency = r.rates.find(rr => rr.symbol == this.form.to);
            let rate;
            if (currency) {
              rate = currency.rate;
            }
            if (rate) {
              exchangeRates.push(rate);
            }
          });this.graphData = {
            labels: rates.map(r => r.date),
            datasets: [
              {
                label: `Exchange Rate ${this.form.from} - ${this.form.to}`,
                backgroundColor: "#f87979",
                data: exchangeRates,
                fill: false
              }
            ]
          };if (exchangeRates.length == 0) {
            this.resultNotFound = true;
          }
        });
    },
    invalidStartDates(evt) {
      return +evt > +new Date();
    },
    invalidEndDates(evt) {
      return +evt < +this.form.startDate || +evt > +new Date();
    }
  },
  beforeMount() {
    this.symbols = Object.keys(this.currencies).map(k => {
      return {
        symbol: k,
        ...this.currencies[k]
      };
    });
    this.toSymbols = JSON.parse(JSON.stringify(this.symbols));
    this.fromSymbols = JSON.parse(JSON.stringify(this.symbols));
  }
};
</script><style>
</style>

To display our line graph in our app, we have to create a component required by Vue Chart.js. Make a file calledLineGraph.vue, and add the following:

<script>
import { Line } from 'vue-chartjs'export default {
  extends: Line,
  props: ['chartdata', 'options'],
  mounted () {
    this.renderChart(this.chartdata, this.options)
  }
}
</script><style>
</style>

Then we make a page to display our quotes. Call it Quotes.vue, and add the following:

<template>
  <div class="page-container md-layout-column">
    <drawer></drawer>
    <md-content>
      <h1 class="center">Quotes as of {{currentTime}}</h1>
      <div class="container">
        <div class="md-layout-item">
          <md-field>
            <label for="movie">Base Currency</label>
            <md-select v-model="baseCurrency" name="baseCurrency">
              <md-option value="EUR">Euro</md-option>
              <md-option value="USD">US Dollar</md-option>
              <md-option value="CAD">Canadian Dollar</md-option>
            </md-select>
          </md-field>
        </div>
        <div>
          <md-content class="md-layout-item" v-for="(q, index) in quoteData.rates" :key="index">
            <h2>{{quoteData.base}} - {{q.symbol| currencyName}}</h2>
            <p>Price: {{q.rate}}</p>
          </md-content>
        </div>
      </div>
    </md-content>
  </div>
</template><script>
import currencies from "../currencies";
import { APIURL } from "../urls";
import * as moment from "moment";
const request = require("superagent");export default {
  name: "quotes",
  data() {
    return {
      quoteData: {},
      currentTime: moment().format("YYYY-MM-DD HH:mm:ss a"),
      baseCurrency: null,
      showNavigation: false
    };
  },
  methods: {
    getQuotes() {
      let url = `${APIURL}/latest`;
      if (this.baseCurrency) {
        url = `${APIURL}/latest?base=${this.baseCurrency}`;
      }
      request.get(url).end((err, res) => {
        this.quoteData = res.body;
        this.quoteData.rates = Object.keys(this.quoteData.rates).map(k => {
          return {
            symbol: k,
            rate: this.quoteData.rates[k]
          };
        });
        this.currentTime = moment().format("YYYY-MM-DD HH:mm:ss a");
      });
    }
  },
  beforeMount() {
    this.getQuotes();
  },
  watch: {
    baseCurrency: function() {
      this.getQuotes();
    }
  }
};
</script><!-- Add "scoped" attribute to limit CSS to this component only -->
<style></style>

In the end, we get the working currency converter app written in Vue:

To build our app for production deployment, run npm run build.

Thank for visiting and reading this article! I'm highly appreciate your actions! Please share if you liked it!

Vue.js Vue Router JavaScript Programming Webdev

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Vue.js Tab Components Based on Vue Router

vue-router-tab .A tab router component based on Vue Router.

8 Popular Websites That Use The Vue.JS Framework

In this article, we are going to list out the most popular websites using Vue JS as their frontend framework. Vue JS is one of those elite progressive JavaScript frameworks that has huge demand in the web development industry. Many popular websites are developed using Vue in their frontend development because of its imperative features.

How to through creating a client-side Vue JS app using Vue Router

Building a Vue v2 JS app using Vue-router. Vue JS is a javascript framework which has recently released version 2. Allowing you to display, manipulate and navigate data, Vue is a React, Knockout and Angular competitor. This blog post walks through building a web app with Vue Router.

How to Use Vue Router with Vue.js

This is what makes navigation easy and really nice in your web applications. So in this article, I'll explain How to Use Vue Router with Vue.js

Vue.js JWT Authentication with Vuex and Vue Router

Build a Vue.js JWT Authentication application using Vuex, Vue Router, VeeValidate - JWT authentication with Vue, Vuex, Vue Router that supports VeeValidate