$parsers and $formatters are being called only once, instead of on every value update

1.2k Views Asked by At

I'm trying to create a directive named currency that appends a $ before the text in input. The dollar sign should be shown at all times and shouldn't be possible to remove.

Here's my code:

app.directive('currency', function() {
    return {
    restrict: 'A',
    require: 'ngModel',
    link: function (scope, elem, attrs, controller) {

      // view -> model
      controller.$parsers.push(function (viewValue) {
        viewValue = viewValue.replace(/^\$/, '');
        controller.$viewValue = viewValue;
        return viewValue;
      });

      // model -> view
      controller.$formatters.push(function (modelValue) {
        modelValue = '$' + modelValue;
        controller.$modelValue = modelValue;
        return modelValue;
      });
    }
  };
});

Working example: https://jsfiddle.net/U3pVM/29012/

As you can see, the dollar sign is appended initially, but can be deleted and won't be appended after that. It seems that the function I push to $formatters is only being called once. Is it supposed to work like that or am I missing something? How can I implement the desired behavior?

2

There are 2 best solutions below

0
On BEST ANSWER

ok, i have tried a workaround, it works but i am not sure if this is the correct way to do it.

updated fiddle : https://jsfiddle.net/U3pVM/29014/

controller.$parsers.push(function (viewValue) {
      //console.log(viewValue.substring(0,1));

      if(viewValue.substring(0,1) != "$"){
            var view_value = "$" + viewValue;
          controller.$setViewValue(view_value);
          controller.$render();
      }
        viewValue = viewValue.replace(/^\$/, '');
        //controller.$viewValue = viewValue;


        console.log(viewValue);
        return viewValue;
      });

P.S: i am not sure why you are injecting ngModel as controller in your link function. it might be a mistake.

1
On

I think you're not quite understanding what the $parsers and $formatters do. Whenever you enter something in the input field, the $parsers are responsible for converting this value into a model value. Formatters are responsible for converting a model value into a display value in your input field.

What you are attempting to do is to change the content of your input field ($formatter feature) when someone enters something into the field ($parser feature).

While I'm sure there are workarounds to making it work this way, you're misusing the concepts of $parsers and $formatters when you do. Instead you should be looking at a custom directive (or extend the one you have) to add to the input that does what you're trying to do, for instance by handing keyups.

Edit

See the following code example for a link function to give you some indication of what I mean:

link: function (scope, elem, attrs, controller) {

  elem.bind('keyup', function(evt) {
     // Change the displayed value after every keypress
     // This function is an example and needs serious work...
     // Perhaps you should even put this in a separate directive
     var value = elem.val().replace(/[^$0-9]/g, '');
     if (value && value.substring(0,1) !== '$') {
        value = '$' + value;
     }
     elem.val(value);
  });

  // view -> model
  controller.$parsers.push(function (viewValue) {
    // Any time the view changes, remove the $ sign and interpret the rest as number for the model
    var modelValue = viewValue.replace(/^\$/, '');
    return parseFloat(modelValue);
  });

  // model -> view
  controller.$formatters.push(function (modelValue) {
    // Any time the model (number) changes, append it with a $ sign for the view
    var viewValue = '$' + modelValue;
    return viewValue;
  });
}

Or check the entire fiddle: https://jsfiddle.net/cL0hpvp4/