Monday, 19 September 2016

Directive to Parse and Format Number from and to Currency

This post will show an example of an AngularJS directive that will automatically format a plain number inserted into an input field into a currency format. As the number is typed into the input field, the value will be formatted into its local currency representation. The directive also converts a number value from model to its currency format in the view.
$formatters, $parsers and $filter are used as part of the codes.
myApp.directive('formatCurrency', ['$filter', '$locale', function ($filter, $locale) {
    return {
        require: '?ngModel',
        link: function (scope, elem, attrs, ctrl) {
            if (!ctrl) return;

            // $formatters is used to process value from code to view
            ctrl.$formatters.unshift(function (modelValue) {
                var formattedValue;
                if (modelValue) {
                    formattedValue = $filter('currency', null, 2)(modelValue);  // use $filter to do some formatting
                } else {
                    formattedValue = '';
                }
                return formattedValue;
            });

            // $parsers is used to process value from view to code
            ctrl.$parsers.unshift(function (viewValue) {
                var plainNumber;
                var formattedValue;
                
                var decimalSeparatorIndex = viewValue.lastIndexOf($locale.NUMBER_FORMATS.DECIMAL_SEP);  // $locale.NUMBER_FORMATS.DECIMAL_SEP variable is the decimal separator for the current culture
                if (decimalSeparatorIndex > 0) {
                    // if input has decimal part
                    var wholeNumberPart = viewValue.substring(0, decimalSeparatorIndex);
                    var decimalPart = viewValue.substr(decimalSeparatorIndex + 1, 2);
                    plainNumber = parseFloat(wholeNumberPart.replace(/[^\d]/g, '') + '.' + decimalPart).toFixed(2); // remove any non number characters and round to two decimal places

                    formattedValue = $filter('currency', null, 2)(plainNumber);
                    formattedValue = formattedValue.substring(0, formattedValue.lastIndexOf($locale.NUMBER_FORMATS.DECIMAL_SEP) + 1);
                    formattedValue = formattedValue + decimalPart;
                } else {
                    // input does not have decimal part
                    plainNumber = parseFloat(viewValue.replace(/[^\d]/g, ''));
                    formattedValue = $filter('currency', null, 0)(plainNumber);     // the 0 argument for no decimal does not work (issue with Angular)

                    if (formattedValue) {
                        // remove the decimal part
                        formattedValue = formattedValue.substring(0, formattedValue.lastIndexOf($locale.NUMBER_FORMATS.DECIMAL_SEP));
                    } else {
                        formattedValue = viewValue;
                    }
                }

                elem.val(formattedValue);
                return plainNumber;
            });
        }
    };
}]);

To use it on an input field:
<input type="text" ng-model="vm.myVariable" format-currency/>

For a working example, please see this on Plunker.

Friday, 2 September 2016

Passing Controller Function to Directive

Below is an example of how to call a controller function from inside an AngularJS directive:
myApp.directive('myDirective', function () {
    return {
        scope: { controllerFunction: '&callbackFunction' },
        link: function (scope, element, attrs) {
            scope.controllerFunction({ arg: '123' });
        },
    }
});
And the markup:
<div ng-controller="MyCtrl as vm">
      <span my-directive callback-function="vm.theControllerFunction(arg)" ></span>
</div>

We can also call the controller function whenever a value in the directive changes. An example of such directive:
myApp.directive('observeValueChange', function () {
    return {
        scope: { controllerFunction: '&callbackFunction' },
        link: function (scope, element, attrs, ngModel) {
            attrs.$observe('theValue', function (newValue) {
                scope.controllerFunction({ arg: newValue });
            })
        },
    }
});
The markup:
<input type="text" ng-model="vm.myVariable" />
      <span observe-value-change the-value="{{vm.myVariable}}" callback-function="vm.theControllerFunction(arg)" ></span>

See the working example in Plunker.