9

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

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

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

4

2 回答 2

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

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

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

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

如果您需要能够在输入之外修改这些字段,则可以添加如下所示的范围或控制器函数:

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

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

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

于 2013-07-13T03:42:28.900 回答
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);
});
于 2013-07-13T18:16:20.500 回答