dendimiiii
dendimiiii

Reputation: 1689

Angular directive with formatters and parsers. Not sure why this is working

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?

Upvotes: 2

Views: 1037

Answers (1)

Walfrat
Walfrat

Reputation: 5353

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.

Upvotes: 2

Related Questions