Muffin
Muffin

Reputation: 791

Data Binding to convert mm to ft and inches or vice versa using Angularjs and ng-model

I want to convert lenghts from mm to ft and inches or vice versa. User can input either mm or ft&in.The case is I always want to save data in mm to database but use angular to see it in both format.

I have already created a solution for it. http://plnkr.co/edit/thgx8vjsjxwfx6cLVVn1?p=preview But it is using ng-change everytime to convert the values.

I was wondering if some angular expert has better idea of doing similar stuff. Please note that I am only planning to save single value $scope.lengthmm

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

    app.controller('MainCtrl', function($scope) {
 // $scope.lengthtodb = 0;

  $scope.mmToFtIn =function(){

    toInch = $scope.lengthmm * 0.0393701;
    toFt = toInch/12;
    OutputFT = Math.floor(toFt);
    OutputInch = (toFt - Math.floor(toFt))*12;
    $scope.lengthft = OutputFT;
    $scope.lengthin = OutputInch;


    $scope.Result1 = OutputFT + "ft" + OutputInch + "Inches";


  };

   $scope.FtInTomm =function(){

    tomm = (($scope.lengthft *12)+  $scope.lengthin)*25.4;
    $scope.lengthmm = tomm;

     $scope.Result2 = tomm;


  };

});

In addition, as there will be lots of fields using the the same logic maybe method mmToFTIn needs to split in two methods to bind ft and inches separately. But I am really looking forward to see a smarted solution.

Upvotes: 0

Views: 2848

Answers (3)

Arlen Beiler
Arlen Beiler

Reputation: 15876

Here is an improvement I made to Christoph Hegemann's answer.

The directive only uses one attribute, which points to a scope object inchesShowFeet (you can call it anything you want).

<input type="text" ng-model="data.beamLength" 
       ng-enhanced-input="inchesShowFeet" 
       ng-model-options="{ debounce: 500 }" />

The directive itself, as he mentioned, uses the ng-model attribute's parsers

app.directive('ngEnhancedInput', function($parse){
  return {
    restrict: 'A',
    require: 'ngModel',
    link : function(scope, element, attr, ngModelCtrl){
      ngModelCtrl.$parsers.unshift(scope.$eval(attr.ngEnhancedInput).store);
      ngModelCtrl.$formatters.unshift(scope.$eval(attr.ngEnhancedInput).show);
    }
  };
});

The object, usually set in the controller, has a show and a store function. The name is funny, but it's the best I could think of. The one returns the value to show in the text box, the other one returns the value to store in the model.

$scope.inchesShowFeet = {
    show: function(val){ return val / 12; },
    store: function(val){ return val * 12; }
};

So, say I have 25 inches stored in my model. The show function get's called with a val of 25. It divides it by 12 and returns it, which get's displayed in the text box. When you type in 4, the store function get's called with a val of 4. It multiplies it by 12 and returns 48, which get's stored in the model.

Upvotes: 0

user2882597
user2882597

Reputation: 424

One option is to create a $watch on the millimeter variable and then update the inches model. This is probably a better option than ng-change because the $watch will fire any time the variable is changed within the scope, not just when the input to your text box changes.

I have created an example for you. While you state you only wish to use one variable, I believe using a second variable in this manner is really the better approach. Alternatively you could use an object or an array to maintain a single variable while storing two values.

For this example I defined the variables as follows

    $scope.inches = 0;
    $scope.mm = 0;

Then we just create a watch on both variables. To reiterate, this function gets called any time there is a change to the mm variable which allows you to ensure that the relationship between mm and inches is maintained.

    $scope.$watch('mm', function(newVal, oldVal) {
      $scope.inches = newVal / 25.4;
    });

You could also create a custom filter as follows... this might even be a better solution if you only need to display mm in a formatted manner (as opposed to using it for calculations or something else).

angular.module('myFilters', []).filter('mmToInches', function() {
  return (function (input) {
    return (input / 25.4);
  });
});

Then later on do...

Inches Filter : {{ mm | mmToInches }}

You can add arguments to filters to a single filter can do both conversions.

angular.module('myFilters', []).filter('convert', function() {
  return (function (input, type) {
    if (type == 'mm2inch')
    {
      return (input / 25.4);
    } else if (type == 'inch2mm') {
      return (input * 25.4);
    }
  });
});

Inches Filter : {{ mm | convert:'mm2inch' }}
MM Filter : {{ inches | convert:'inch2mm' }}

Upvotes: 0

Christoph Hegemann
Christoph Hegemann

Reputation: 1434

Formatting the Output onto the view is best done with filters.

JS:

app.filter('inchFilter', function() {
  return function(input) {
    return Math.floor(input * 0.0393701);
  };
});

HTML:

<input name="mm" type="text" value="{{lengthmm | inchFilter}}">

Edit:

For a more complete and powerful solution I extended the plunker with a directive to now allow two-way-binding on the non-metric fields aswell.

app.directive('enhancedInput', function($parse){
  return {
    restrict: 'A',
    require: 'ngModel',
    link : function(scope, element, attr, ngModelCtrl){
      ngModelCtrl.$parsers.unshift(scope.$eval(attr.fromMm));
      ngModelCtrl.$formatters.unshift(scope.$eval(attr.toMm));
    }
  };
});

This is achieved by first "requiring" the ngModelController and then using its $parsers and $formatters to intercept communication in between model and view.

plunker

Upvotes: 2

Related Questions