2

I need to make it possible to select a weight in kg and pounds. But my modelValue should always contain the value in kilograms.

So i created the following HTML.

<div>
   <label>{{'aircraftModal.mtom' | translate}}:</label>
   <label><input type="radio" name="weightRadio" ng-model="AircraftCtrl.weightRepresentation" ng-value="'kg'" >Kilogram</label>
   <label><input type="radio" name="weightRadio" ng-model="AircraftCtrl.weightRepresentation" ng-value="'pound'">Pound</label>
   <input type="number" step="0.01" ng-model="AircraftCtrl.mtom" weight weight-representation="AircraftCtrl.weightRepresentation">
</div>

And I made a directive weight with an attribute weigt-representation. This directive will parse/format the values I have in my view/controller.

angular.module('app.directives').directive('weight', function () {
return {
    require: 'ngModel',
    restrict: 'A',
    scope: {
        weightRepresentation: "=weightRepresentation"
    },
    link: function(scope, elem, attrs, ngModelController) {
        var conversionPoundsToKilogram = 0.45359237;

         ngModelController.$formatters.unshift(function(valueFromModel) {
             if(!valueFromModel) return valueFromModel;
             if(scope.weightRepresentation === 'pound'){
                valueFromModel = valueFromModel / conversionPoundsToKilogram;
             }
             return parseFloat(valueFromModel).toFixed(2);
         });

         ngModelController.$parsers.push(function(valueFromInput) {
             if(!valueFromInput) return valueFromInput;
             if(scope.weightRepresentation === 'pound'){
                valueFromInput = valueFromInput * conversionPoundsToKilogram;
             }
             return valueFromInput;
         });

         scope.$watch('weightRepresentation', function(newWeight, oldWeight){
             if(ngModelController.$modelValue){
                 console.log("before " + ngModelController.$modelValue);
                 ngModelController.$modelValue = ngModelController.$modelValue - 1;
                 console.log("After" + ngModelController.$modelValue);
             }
         });
    }
};
});

The problem is that whenever I change radiobuttons, I need to re-format the viewValue. So I should only rerun the formatters. I red that formatters only get exectued when the modelValue is changed. For that reason I added ngModelController.$modelValue = ngModelController.$modelValue - 1;. The formatters do get called, but the valueFromModel does not contain the edited value, but the value before the minus 1.

Tbh, its working perfectly to my needs, but I don't understand why it's working

Also, the modelValue could have any amount of fraction digits, and the viewValue should be fixed to 2 fraction digits

My question: 1. Why is behaviour occuring? 2. Is this the right way to induce the re-run of formatters without changing the actual modelValue, or is this just a dirty hack?

dendimiiii
  • 1,659
  • 3
  • 15
  • 26
  • 1
    Changing `ngModelController.$modelValue` will change your model copy, which will trigger the `$formatters` pipeline. But this pipeline will take as parameter your real model value. (That is not updated yet). – gr3g Mar 11 '16 at 08:20

1 Answers1

2

This is a dirty hack, use $setViewValue to change the viewValue, since you implements the $parsers that will be called when viewValue is changed it'll work fine.

About what you do work : the ngModelController has a watch on his ngModel so it can refresh the view if you change it.

EDIT : add sample code from my last comment which is the one accepted by the author : using a flag to not change ngModel if the last change was just a change of unit :

var unitChanged = false;

scope.$watch('weightRepresentation', function(newWeight, oldWeight){
         if(ngModelController.$modelValue){
             unitChanged = true;
             // compute new viewValue and update it using $setViewValue
             [...]
         }
     });

ngModelController.$parsers.push(function(valueFromInput) {
         if(!valueFromInput) return valueFromInput;
         if(unitChanged){
               unitChanged = false;
               return ngModelControler.$modelValue;
         }
         [...]// normal code
     });

Note order of those 3 javascript instructions does not count, i just order it like this to be more readable has an answer.

Walfrat
  • 5,363
  • 1
  • 16
  • 35
  • The flow : model = "2.1111111", `$setViewValue(2.111111)` But `$viewValue` needs to be fixed on 2 decimals and give back to the model the 'real' value : "2.111111" – gr3g Mar 11 '16 at 08:26
  • $setViewValue will (after $render()) recalculate the modelValue. I am working with floats. for example 2000kg -> 4409,25xxxxx pounds. And when I click back to kg, it will use 4409,25xxxxx to recalculate, which will be 2000.000000002. So any approach with recalculating modelValue will not work. – dendimiiii Mar 11 '16 at 08:26
  • then he can just externalize the function that he pushed in $formatters, and use it in the $watch in order to set the proper $viewValue, the $modelValue the $parsers will get called but will set the same value if it was only a change of unit, i don't see any problem. – Walfrat Mar 11 '16 at 08:33
  • Here is how you can round up to the xth decimal : http://stackoverflow.com/questions/11832914/round-to-at-most-2-decimal-places-in-javascript set it to like 5th decimal and you should be fine – Walfrat Mar 11 '16 at 08:34
  • 1
    If this really doesn't fit for you, in the $watch, set a flag to true, recompute the $viewValue as i said and in the $parser, have the following like code : if(flag==true){flag=false; return ngModelCtrl.$modelValue;} This will permit you to not recompute the modelValue if only the unit has changed – Walfrat Mar 11 '16 at 08:38
  • This last comment did the trick. Update your answer nd I will gladly give you the accepted. – dendimiiii Mar 11 '16 at 09:26