AngularJS中的华氏度和摄氏度双向转换

Dav*_*ird 9 data-binding angularjs

在AngularJS中,如何$ watch $ scope变量并使用它来更新另一个变量是很简单的.但是,如果两个范围变量需要相互观察,那么最佳实践是什么?

我有一个双向转换器,将华氏温度转换为摄氏温度,反之亦然.如果你在华氏度中键入"1",它会正常工作,但尝试"1.1"并且Angular会在覆盖你刚刚输入的华氏度之前稍微反弹一下(1.1000000000000014):

function TemperatureConverterCtrl($scope) {
  $scope.$watch('fahrenheit', function(value) {
    $scope.celsius = (value - 32) * 5.0/9.0;
  });
  $scope.$watch('celsius', function(value) {
    $scope.fahrenheit = value * 9.0 / 5.0 + 32;
  });
}
Run Code Online (Sandbox Code Playgroud)

这是一个吸烟者:http://plnkr.co/edit/1fULXjx7MyAHjvjHfV1j?p = preview

有哪些不同的可能方法可以阻止Angular"弹跳"并强制它使用您按原样键入的值,例如使用格式化程序或解析器(或任何其他技巧)?

Ove*_*ous 9

我认为最简单,最快速和最正确的解决方案是使用一个标志来跟踪正在编辑的字段,并且只允许来自该字段的更新.

您所需要的只是使用该ng-change指令在正在编辑的字段上设置标志.

工作插件

必要的代码更改:

将控制器修改为如下所示:

function TemperatureConverterCtrl($scope) {
  // Keep track of who was last edited
  $scope.edited = null;
  $scope.markEdited = function(which) {
    $scope.edited = which;
  };

  // Only edit if the correct field is being modified
  $scope.$watch('fahrenheit', function(value) {
    if($scope.edited == 'F') {
      $scope.celsius = (value - 32) * 5.0/9.0;
    }
  });
  $scope.$watch('celsius', function(value) {
    if($scope.edited == 'C') {
      $scope.fahrenheit = value * 9.0 / 5.0 + 32;
    }
  });
}
Run Code Online (Sandbox Code Playgroud)

然后将这个简单的指令添加到输入字段(使用FC适当):

<input ... ng-change="markEdited('F')/>
Run Code Online (Sandbox Code Playgroud)

现在只有输入的字段可以改变另一个字段.

如果您需要能够输入修改这些字段,则可以添加看起来像这样的范围或控制器函数:

$scope.setFahrenheit = function(val) {
  $scope.edited = 'F';
  $scope.fahrenheit = val;
}
Run Code Online (Sandbox Code Playgroud)

然后将在下一个$ digest周期更新Celsius字段.

此解决方案只有最少的额外代码,消除了每个周期多次更新的可能性,并且不会导致任何性能问题.


jps*_*ons 7

这是一个非常好的问题,我正在回答它,即使它已被接受:)

在Angular中,$ scope是模型.该模型是存储您可能希望在应用程序的其他部分中保留或使用的数据的地方,因此,它应该设计为具有良好的模式,就像在数据库中一样.

您的模型有两个冗余的温度字段,这不是一个好主意.哪一个是"真正的"温度?有时你想要对模型进行非规范化,只是为了访问效率,但是当值是幂等时,这只是一个选项,正如你所发现的那样,这些并不是由于浮点精度.

如果你想继续使用该模型,它将看起来像这样.你选择一个或另一个作为"真实来源"温度,然后将另一个输入作为带有格式化程序和解析器的便利输入框.假设我们想在模型中使用华氏温度:

<input type="number" ng-model="temperatureF">
<input type="number" ng-model="temperatureF" fahrenheit-to-celcius>
Run Code Online (Sandbox Code Playgroud)

转换指令:

someModule.directive('fahrenheitToCelcius', function() {
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, ngModel) {
      ngModel.$formatters.push(function(f) {
        return (value - 32) * 5.0 / 9.0;
      });
      ngModel.$parsers.push(function(c) {
        return c * 9.0 / 5.0 + 32;
      });
    }
  };
});
Run Code Online (Sandbox Code Playgroud)

有了这个,你就可以避免"蹦蹦跳跳",因为$ parsers只能运行用户操作而不是模型更改.你仍然有很长的小数,但这可以用舍入来弥补.

但是,听起来我不应该为此使用模型.如果你想要的只是"每个盒子更新另一个盒子",你可以做到这一点,甚至没有模型.这假设输入框开始是空白的,它只是用户转换温度的简单工具.如果是这种情况,你就没有模型,没有控制器,甚至根本就没有使用Angular.这是一个纯粹的基于视图的小部件,它基本上只是jQuery或jQLite.但它的用处有限,因为没有模型它不会影响Angular中的任何其他内容.

要做到这一点,你可以制作一个temperatureConverter带有几个输入框的模板的指令,并观察这两个框并设置它们的值.就像是:

fahrenheitBox.bind('change', function() {
  celciusBox.val((Number(fahrenheitBox.val()) - 32) * 5.0 / 9.0);
});
Run Code Online (Sandbox Code Playgroud)