2013-07-13 31 views
9

在AngularJS中,如何$ $觀察一個$ scope變量並使用它來更新另一個變量是很簡單的。但是如果兩個範圍變量需要彼此關注,那麼最佳做法是什麼?AngularJS中的華氏和攝氏雙向轉換

我有一個雙向轉換器,將華氏溫度轉換爲攝氏溫度,反之亦然。如果你輸入「1」到華氏,但嘗試「1.1」和角度會​​反彈,一點點覆蓋華氏之前它的工作好了,你剛剛輸入是一個稍微不同的值(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「反彈」並迫使它使用您鍵入的值,例如通過使用格式化程序或解析器(或任何其他技巧)?

+1

這是一個很好的問題的原因是因爲這種情況有遍及它的無限循環 - 如果計算不是彼此逆... +1。我會很驚訝地發現任何其他不同的答案。 – rGil

+0

由於DSP的背景,我很難拒絕進入這個數學,所以我會放棄它:這可能會進入一個無限循環(除了Angular有一個內置的最大迭代次數限制)。這裏是一個很好的解釋,爲什麼值「反彈」(由於骯髒檢查):http://stackoverflow.com/a/9693933/885163。並且,當與浮點的精度限制相結合時,這些值有可能在無限期內振盪0次,1次或更多次,這種效應在IIR DSP濾波器中稱爲「極限循環」。 * phew *如何評論? –

回答

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

然後攝氏場將在接下來的更新$消化週期。

該解決方案只有最少的額外代碼,消除了每個週期多次更新的機會,並且不會導致任何性能問題。

+0

如果有人認爲我在底部的無關筆記不合適,我會刪除它。請讓我知道。謝謝。 – OverZealous

+1

你的答案是超級。我已經把你的額外記錄放在了一邊,並且很感激。我會編輯我的問題以取出(或重新填寫)額外需求。 –

+0

真棒,很高興你沒有被冒犯! :-)現在,我自己將剝離那一點。 – OverZealous

7

這是一個非常好的問題,我回答它,即使它已經接受了:)

在折角,在$範圍是模型。該模型是一個存儲數據的地方,您可能想要在應用程序的其他部分保留或使用數據,因此,它應該設計爲具有良好的架構,就像在數據庫中一樣。

您的模型有兩個用於溫度的冗餘字段,這不是一個好主意。哪一個是「真正的」溫度?有時候您想要對模型進行非規範化,只是爲了提高訪問效率,但只有在值爲冪等時纔是真正的選擇,正如您發現的那樣,這些不是由於浮點精度。

如果你想繼續使用模型,它會看起來像這樣。您可以選擇其中一個作爲「真相源」溫度,然後將其他輸入作爲帶格式化程序和解析器的便捷輸入框。比方說,我們希望華氏模型:

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

有了這個,你會避免「蹦跳」,因爲$解析器僅在用戶操作運行,而不是模型更改。你仍然有很長的小數,但可以通過舍入來彌補。

但是,這聽起來像你不應該使用這個模型。如果所有你想要的是「每個盒子更新另一個盒子」,你可以做到這一點,甚至沒有一個模型。這假定輸入框從空白處開始,它只是用戶轉換溫度的簡單工具。如果是這樣的話,你就沒有模型,也沒有控制器,甚至根本不會使用Angular。這是一個純粹基於視圖的小部件,它基本上只是jQuery或jQLite。儘管它的用處有限,因爲在沒有模型的情況下,它不會影響Angular中的其他任何東西。

要做到這一點,您可以製作一個temperatureConverter指令,該指令具有包含幾個輸入框的模板,並觀察這兩個框並設置它們的值。像這樣:

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

這個答案對於爲$ scope設計一個正確的規範化模式提供了一個很好的觀點;無論是標記和守衛(@OverZealous的答案)還是更好的模式設計都是最好的,可能會因應用程序而異,而且這兩個答案都非常棒。 –