Dev Tips & Tricks

Javascript floating number precision using Vue.js filters and BigNumber.js

Back to posts

Master currency-formatting for real-world production applications with this elegant strategy.

JavaScript has grown a lot. Today we use it not only for small business websites, but for complex large-scale applications as well. Sometimes we handle issues that are really important for a business, such as buying or transferring money, and we must think about robust solutions for the long-term.

For those who need to perform currency calculations on the front-end and are struggling with JavaScript floating number precision, I hope this strategy helps you as much as it did me.

You can get the code for this tutorial from the repository below.

https://github.com/2amigos/vue-currency-filters

Installing Vue.js

If you are already working with Vue.js and you have it installed, skip to the next step “Set up the filters strategy”.

First, using the command line, let’s install the Vue-Cli.

npm install -g @vue/cli
# OR
yarn global add @vue/cli

# Next, create your project.

vue create my-project

You should see a display that looks like the screen shot below.

For the sake of simplicity, I’ll go with default, but you can use the preset you like most or manually select the features you need. Don’t worry. You can also extend the features later.

That’s it! Now you have a fully working Vue.js project with a single command.

Check the documentation for more information about Vue-CLI.
Set up the filters strategy

This is a required step to become a Vue.js master!

Modularization is amazing! Developers have learned that by separating functions in small files, maintenance and testing are easier. This is the next step with filters. You are going to create a file for each “type” of filter you have.

I separated my own filters in three categories — date, string, and currency.

Now, create a filters folder under my-project/src and create a _globals.js file inside.

In the file (_global.js) you will find all the filter files in the directory and add them to Vue.filter. This allows you to use them globally in your app.

Add this code to src/filters/_global.js


import Vue from 'vue';

// Get all the javascript files from the directory where this file is.
const filters = require.context('.', true, /\.(js)$/i);

// This will be our regular expression to match against the files.
const nameReg = /([a-z0-9]+)\./i;

filters.keys().forEach((key) => {
 const name = key.match(nameReg)[1];

 // If the file name is globals we ignore it.
 if (name === 'globals') return;

 // In case the file only exports one default function we add it to Vue.filter
 if (filters(key).default) {
   Vue.filter(key, filters(key).default);
 } else {

   // We iterate over each export function and add it to Vue.filter
   Object.keys(filters(key)).forEach((filter) => {
     if (Object.prototype.hasOwnProperty.call(filters(key), filter)) {
       Vue.filter(filter, filters(key)[filter]);
     }
   });
 }
});

Finally, to execute this file you need to import it somewhere in your app. The perfect place to do so is your entry file main.js.


import Vue from 'vue'
import App from './App.vue'

// ‘@’ is an alias for src folder. To know more about webpack alias check this link 
// https://webpack.js.org/configuration/resolve/
import '@/filters/_globals';

Vue.config.productionTip = false

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

After adding this code, you can add any file under the filters folder and it will be automatically added to Vue filters.
Install BigNumber and create the currency filters

Now it’s time to add BigNumber.js dependency to your project. Do this by running the next command.


npm install bignumber.js
# OR
yarn add bignumber.js

Next, create a file under filters folder called currency.js

In this file you can add the filters you want. For example, you might include fiat and cryptocurrency values.

import { BigNumber } from 'bignumber.js';

// This function creates a bignumber clone with a specific configuration.
function createBigNumberObject({decimalSeparator = ',', groupSeparator = '.', groupSize = 3} = {}) {
 return BigNumber.clone({
   decimalSeparator,
   groupSeparator,
   groupSize,
 });
}

// Create the big number clones.
const bnEUR = createBigNumberObject({ decimalSeparator: ',', groupSeparator: '.'});

const bnUSD = createBigNumberObject();

const bnCRYPTO = createBigNumberObject();

// This function accepts a value and a config object and creates a custom formatter
function createFormatter(value, { bigNumberObject = bnCRYPTO, precision = 2, symbol = null } = {}) {
 return {
   format() {
     const result = bigNumberObject(value).toFormat(precision);

     // If there is a symbol property in the config object we add the symbol at the beginning of the string
     if (symbol) return result.insert(0, symbol);

     return result;
   }
 }
}

const USD = value => createFormatter(value, { bigNumberObject: bnUSD, symbol: '$'});
const EUR = value => createFormatter(value, { bigNumberObject: bnEUR, symbol: '€'});
const CRYPTO = (value, precision = 8) => createFormatter(value, { precision });

// We will access this container objects later on our filter functions
const CURRENCY_FORMATTER = {
 USD,
 EUR,
};
const NO_SYMBOL_CURRENCY_FORMATTER = {
 bnUSD,
 bnEUR,
};

// This filter gets the user selected currency and formats your value to match that currency.
export function currency(value) {
 // Normally we would use Vuex but for the sake of this example I will use localstorage
 // to retrieve the user selected currency. Needs to be one of EUR|USD.
 const userSelectedCurrency = localStorage.getItem('User/selectedCurrency');

 return CURRENCY_FORMATTER[userSelectedCurrency](value).format();
}

// This filter does the same but returns the formatted value without symbol.
export function currencyWithoutSymbol(value) {
 const userSelectedCurrency = localStorage.getItem('User/selectedCurrency');

 return NO_SYMBOL_CURRENCY_FORMATTER[`bn${userSelectedCurrency}`](value).toFormat(2);
}

// This filter allow you to format a different fiat currency than the one
// selected by the user.
export function toCurrency(value, symbol) {
 return CURRENCY_FORMATTER[symbol](value).format();
}

// This filter takes a value and optionally adds a symbol and converts the value into
// crypto currency format
export function crypto(value, symbol = '') {
 let precision;

 if (symbol === 'ETH' || symbol === 'ETC') {
   precision = 16;
 } else if (symbol === 'XRP') {
   precision = 6;
 }

 return CRYPTO(value, precision).format();
}

// Filter utilities
export function toFloat(value, places = 2) {
 return parseFloat(value).toFixed(places);
}

export function toBigNumber(value) {
 return new BigNumber(value);
}

export function bnAbs(bigNumber) {
 return bigNumber.absoluteValue();
}

export function bnRemoveTrailingZeros(bigNumber) {
 return bigNumber.toString();
}

export function bnToFixed(bigNumber, places = 2) {
 return bigNumber.toFixed(places);
}

export function bnMultiply(bigNumber, value) {
 return bigNumber.multipliedBy(value);
}

export function bnPlus(bigNumber, value) {
 return bigNumber.plus(value);
}

export function bnMinus(bigNumber, value) {
 return bigNumber.minus(value);
}

Format currencies like a god with filter chaining

Now that you can use your filters everywhere with several useful filters created for currencies, it’s time to use them.

In your template, use the filters like this.

{{ 2.42 | toBigNumber | bnPlus(3) | bnRemoveTrailingZeros }}

Here is an item to consider. If you want to use bigNumber functions like bnPlus, bnMinus, or bnAbs, you need to cast the number to a big number object with the toBigNumber filter.

The bnRemoveTrailingZeros filter is used to call toString() from the big number object.

Summary

You can do powerful things with Vue.js filters. This is one way to take advantage of that power. If you and your team get use to use these filters on your Vue.js single-file-components you can have a standard way of formatting and avoid a lot of small bugs in the future.

Remember you can check the code in this repository.

https://github.com/2amigos/vue-currency-filters and play around with it.

Share with
Terms of Use | Privacy Policy