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

时间:2013-07-13 01:47:43

标签: data-binding angularjs

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

我有一个双向转换器,将华氏温度转换为摄氏温度,反之亦然。如果你键入" 1"它可以正常工作进入华氏温度,但尝试" 1.1"和Angular会在覆盖你刚刚输入的Fahrenheit之前稍微反弹一下(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;
  });
}

这里是一名掠夺者:http://plnkr.co/edit/1fULXjx7MyAHjvjHfV1j?p=preview

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

2 个答案:

答案 0 :(得分:9)

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

您只需使用ng-change指令在正在编辑的字段上设置标记。

Working Plunk

必要的代码更改:

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

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;
    }
  });
}

然后将此简单指令添加到输入字段(根据需要使用FC):

<input ... ng-change="markEdited('F')/>

现在只输入的字段可以更改另一个字段。

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

$scope.setFahrenheit = function(val) {
  $scope.edited = 'F';
  $scope.fahrenheit = val;
}

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

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

答案 1 :(得分:7)

这是一个非常好的问题,即使它已被接受,我也会回答它。)

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

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

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

<input type="number" ng-model="temperatureF">
<input type="number" ng-model="temperatureF" fahrenheit-to-celcius>

转换指令:

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;
      });
    }
  };
});

有了这个,你就可以避免“弹跳”,因为$ parsers只能运行用户操作而不是模型更改。你仍然有很长的小数,但这可以通过舍入来解决。

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

要做到这一点,你可以制作一个temperatureConverter指令,其中包含一个带有几个输入框的模板,并观察这两个框并设置它们的值。类似的东西:

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