Angular model.$viewValue not showing in input fiel

2019-09-06 11:48发布

问题:

I previously write this [question][1], Inow I have the problem that the model.$viewValue it's not the same of the value the i see in the input box.

<div amount-input-currency="" ng-model="data.amount" ></div>

This is my directive (isNumeric and similar is not important that works weel):

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.data = { amount: ''};
});
app.directive('amountInputCurrency', function () {            
  return {
    restrict: 'EA',
    require: 'ngModel',
    templateUrl: 'inputCurrency.tmpl.html',
    scope: {
      model: '=ngModel',
    },
    link: function (scope, elem, attrs, ngModelCtrl) {
      scope.model2 = ngModelCtrl;
      console.log("I am in the directive!");
      var myAmountCurrencyType = elem.find('.cb-amount-input-currency');

      scope.onFocus = function() {
          removeThousandSeparator();
      };

      scope.onBlur = function() {
          renderValue();
          ngModelCtrl.$render();
      };


      //format text going to user (model to view)
      ngModelCtrl.$formatters.push(function(value) {
        return parseValue(value);
      });

      //format text from the user (view to model)
      ngModelCtrl.$parsers.push(function(value) {
        var num = Number(value);
        if(isNumeric(num)) {
            var decimal = 2;
            return formatAmount();
        } else {
            return value;
        }
      });

      function isNumeric(val) {
        return Number(parseFloat(val))==val;
      }

    }
  }
});

And this is my template:

scope.model: {{model}}<br>
viewValue: {{model2.$viewValue}}<br>
modelValue: {{model2.$modelValue}}<br>
<input type="text" class="amount-input-currency form-control" x-ng-model="model" ng-focus="onFocus()" ng-blur="onBlur()"></input>

回答1:

Set the viewValue using ngModelCtrl.$setViewValue() in order to update the model instead of setting $viewValue field directly. But I am not sure what is the point of using NgModelController in this case at all.

If the only purpose is to format the value of the textbox, manipulate the input element value instead of NgModelController fields.

function renderValue() {
    var myAmountCurrencyType = elem.find('input');
    var value = myAmountCurrencyType.val();

    var decimal = 2;
    if (value != undefined && value !="") {
      myAmountCurrencyType.val(formatAmount());
    }
  }

This way it does not update the model. If you want to have full control over the data binding you can consider removing the binding from the input element x-ng-model="model" and implementing it using NgModelController in your directive.



回答2:

If you can do what you need to do without formatters and parsers it's better because that you work more in the angular way, and if you can avoid requiring ng-model in directives if you can manage without it because that cause a lot of mess too.

As for your problem I will show a solution that does not need the formatters and parsers and I hope that this what you wanted, if you must use the formatters and parsers you can, but it will essentially do the same as the following solution:

index.html:

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.2.x" src="http://code.angularjs.org/1.2.15/angular.js" data-semver="1.2.15"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <!--<div amount-input-currency="" ng-model="data.amount" ></div>-->
    <amount-input-currency model="data.amount"></amount-input-currency>
  </body>

</html> 

amountInputCurrency.tmpl.html:

scope.model: {{model}}<br>
viewValue: {{model2.$viewValue}}<br>
modelValue: {{model2.$modelValue}}<br>
<input type="text" class="cb-amount-input-currency form-control" ng-model="model" ng-focus="onFocus()" ng-blur="onBlur()">

app.js:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.data = { amount: ''};
});
app.directive('amountInputCurrency', function () {
  var isAllowedKey = function (k, v) {
      return (
          k === 8 || k === 9 || k === 46 ||
          (k > 47 && k < 58) ||
          (k > 95 && k < 106) ||
          (k > 36 && k < 41) ||
          (k === 188 && (!v || v.search(/(\.|\,)/)<0)) ||
          ((k === 190 || k === 110) && (!v || v.search(/(\.|\,)/)<0))
      );
  };

  return {
    restrict: 'E',
    // require: 'ngModel',
    templateUrl: 'amountInputCurrency.tmpl.html',
    scope: {
      model: '=',
    },
    link: function (scope, elem, attrs) {
      // scope.model2 = ngModelCtrl;
      console.log("I am in the directive!");
      var myAmountCurrencyType = elem.find('.cb-amount-input-currency');
        myAmountCurrencyType.on('keydown', function (e) {
          //if (!isAllowedKey(e.which, scope.model)) {
          if (!isAllowedKey(e.which, scope.model)) {
              e.preventDefault();
          }
      });

      scope.onFocus = function() {
          removeThousandSeparator();
      };

      scope.onBlur = function() {
          renderValue();
          // ngModelCtrl.$render();
          // scope.model = ngModelCtrl.$viewValue;
      };


      // //format text going to user (model to view)
      // ngModelCtrl.$formatters.push(function(value) {
      //   return parseValue(value);
      // });

      // //format text from the user (view to model)
      // ngModelCtrl.$parsers.push(function(value) {
      //   var num = Number(value);
      //   if(isNumeric(num)) {
      //       var decimal = 2;
      //       return formatAmount(Number(num).toFixed(decimal), decimal, ',', '.');
      //   } else {
      //       return value;
      //   }
      // });

      function isNumeric(val) {
        return Number(parseFloat(val))==val;
      }

      function renderValue() {
        var value = String(scope.model || '');
        var decimal = attrs.cbAmountDecimal || 2;
        if (value != undefined && value !="") {
          scope.model = formatAmount(value, decimal, ',', '.');
          // ngModelCtrl.$render();
        }
      }

      function formatAmount(amount, c, d, t) {
        if (amount.indexOf(',') !== -1) {
            if (amount.indexOf('.') !== -1) {
                amount = amount.replace(/\./g,'');  //remove thousand separator
            }
            amount = amount.replace(/\,/g,'.');
        }
        c = isNaN(c = Math.abs(c)) ? 2 : c;
        d = d === undefined ? "." : d;
        t = t === undefined ? "," : t;
        var n = amount,
            s = n < 0 ? "-" : "",
            i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "",
            j = (j = i.length) > 3 ? j % 3 : 0;
        return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
      }

      function removeThousandSeparator() {
        if(scope.model != undefined && scope.model !="") {
            scope.model = scope.model.replace(/\./g,'');
            // ngModelCtrl.$render();
            // scope.model = ngModelCtrl.$viewValue;
        }
      }

      function parseValue(viewValue) {
        var num = 0;
        if(isNumeric(viewValue)) {
            num = viewValue;
        } else {
            num = viewValue ? viewValue.replace(/,/g,'.') : viewValue;
        }
        return num;
      }

    }
  }
});

If this is not what you want then I'm truly sorry and please comment what is the problem in my solution for and I will try my best to see if I can help.



回答3:

Checkout Formatters and Parser, it's how to do what you want, but you have to require ngModel to hook append formatter or parser.

A good article about them : https://alexperry.io/angularjs/2014/12/10/parsers-and-formatters-angular.html

Regards.