莫名失效的雙向繫結
來自於開發中偶然遇到的一個問題,看下面的例子:
<div ng-app>
<div ng-controller="TodoCtrl">
<input type="number" ng-model="value" size="30" >
<input type="button" ng-click="change()" class="btn-primary" value="TEST" >
</div>
</div>
function TodoCtrl($scope) {
$scope.change = function() {
$scope.value = ''
}
}
可以在 JSFiddle 上直接開啟 demo ,這段代碼的本意是在框裡輸入任意一個數字,然後按 TEST 按鈕,數字都會被清空。
在大多情況下,這段代碼都是生效的,但是當我們輸入 e 時,點擊按鈕,卻等不到任何反應。
HTML 的 number input
標題裡我寫的是 AngularJS 的 number input,而事實上,number input 是 HTML 特性。只允許輸入合法的數字,之所以能夠輸入 e 是因為允許輸入指數,上面的功能我們同樣可以用原生實現:
<script>
function test(){
var input = document.getElementById('number-input')
input.value = ""
}
</script>
<input id="number-input" type="number">
<input type="button" onclick="test();" value="TEST">
然後我們會發現工作的非常好,那麼問題出在哪呢。我們可以試著在這裡類比一下 AngularJS 的髒檢查,即比對 View 和 Model 的值是否不同,也就是說,代碼變成了這樣:
<script>
function test(){
var input = document.getElementById('number-input')
if (input.value !== ''){
input.value = ''
}
}
</script>
<input id="number-input" type="number">
<input type="button" onclick="test();" value="TEST">
然後我們就會發現,成功的複現了上面的 Demo 遇到的問題。這裡問題的原因應該很明顯了,我們也可以輸出 input.value 的值來驗證我們的想法,會發現當輸入非法時(例如單獨的 e ),擷取到的 input.value 就是空,所以 AnguarJS 認為 View 和 Model 沒有任何不同,不需要更新。
驗證我們的想法
要驗證是否是這個原因也非常簡單,只要我們先把 View 的值變成合法值,再將 Modle 置為空白,看看這種情況下 AngularJS 能否正確的清空輸入框:
function Ctrl($scope) {
$scope.change = function() {
$scope.value = 0
$scope.$apply();
$scope.value = null
}
}
正如我們預料的,這次成功的清除了非法輸入
解決方案
最後我們採取的解決方案是當輸入非法時直接使用 DOM 操作清空輸入,雖然 DOM 操作不優雅,但是實際上這裡 DOM 操作並不影響 View 和 Model 之間的關係,因為 AngularJS 本來就認為此時的 VIew 是空的。(我們可以通過 input.validity.valid 來判斷輸入的合法性。)