/ AngularJS

AngularJS - Currency formatted input

Whilst developing a number of applications we’ve come across the need to display currency-formatted data within an input field which is using ngModel.

At first it seems like a potentially tricky task but there’s a pretty simple way to achieve this by implementing a simple directive and making use of ngModelController.

We’ve called our directive blurCurrency as the idea is that once the input blur event is triggered; we want to then display the user input number as currency.

Writing the directive

It’s really simple to do, in your directive’s return Object include ngModel:

return {
 require: '^ngModel',
 scope: true,
 link: link
};

This will allow us to reference ngModelController within our link method:

function link(scope, el, attrs, ngModelCtrl) {

Now we can include our custom formatter by pushing it to the ngModelController.$formatters Array.

function formatter(value) {
      value = value ? parseFloat(value.toString().replace(/[^0-9._-]/g, '')) || 0 : 0;
      var formattedValue = $filter('currency')(value);
      el.val(formattedValue);
            
      ngModelCtrl.$setViewValue(value);
      scope.$apply();

      return formattedValue;
    }
ngModelCtrl.$formatters.push(formatter);

Our method responds to changes to our value via ngModel and reformats the data to our required currency String.

So; if the value is changed from an external source (i.e. not from our input field) then the value will update and reformat accordingly.

Reformatting on user input

In order to reformat our input to display as currency; we need to include our method when the input blur method is invoked.

This is really easy as we have access to our input field within our link method so we can make use of this.

el.bind('blur', function() {
  formatter(el.val());
});

As you can see; we’re making use of our formatter method once more in order to reformat our data nicely without repeating any code.

We also clear the inout field when the focus method is invoked just to clean up the user experience:

el.bind('focus', function() {
  el.val('');
});

You may have noticed that we’re ensuring that we are passing a valid number to the currencyFilter as part of the formatter method with:

value = value ? parseFloat(value.toString().replace(/[^0-9._-]/g, '')) || 0 : 0;

This is in place as the user may enter a currency formatted String (e.g. $35,000) which would otherwise cause the filter to fail.

Final directive

Here’s the full directive source:

(function() {

  'use strict';

  function blurCurrency($filter) {

    function link(scope, el, attrs, ngModelCtrl) {

        function formatter(value) {
            value = value ? parseFloat(value.toString().replace(/[^0-9._-]/g, '')) || 0 : 0;
            var formattedValue = $filter('currency')(value);
            el.val(formattedValue);
            
            ngModelCtrl.$setViewValue(value);
            scope.$apply();

            return formattedValue;
      }
      ngModelCtrl.$formatters.push(formatter);

      el.bind('focus', function() {
        el.val('');
      });

      el.bind('blur', function() {
        formatter(el.val());
      });
    }

    return {
      require: '^ngModel',
      scope: true,
      link: link
    };
  }
  blurCurrency.$inject = ['$filter'];

  angular
    .module('myApp')
    .directive('blurCurrency', blurCurrency);

})();

Using the directive

In order to make use of this; you must then include the directive in your input element.

<input type="text" data-ng-model="vm.money" data-blur-currency />

You will note that the input type is text and not number - unfortunately; as we’re going to reformat the input number as currency - the result will be a formatted String so an input type of number will not work.

JSFiddle

See this directive in action with our jsfiddle.