ng-model-options是angular-1.3新出的一個指令,這篇文章就來介紹這個指令的用法.
ng-model-options允許我們控制ng-model何時進行同步. 比如:1.當某個確定的事件被觸發的時候 2.在指定的防抖動延遲時間之後,這樣檢視值就會在指定的時間之後被同步到模型.
為了瞭解它到底是什麼意思,我們從一個最簡單的ng-model指令建立的input元素雙向資料繫結的栗子開始看起:
eg-0.0
核心程式碼:
<input type="text" ng-model="name"> <p>Hello {{name}}!</p>
點選檢視效果: http://jsfiddle.net/gdjwk5u7/
這是我們都很熟悉的,在angular-1.2版本就可以實現的ng-model雙向繫結.
可以看到,它是實時同步更新的,input中每輸入一個字,它就立刻同步到資料模型,這是因為,每次輸入input,都會觸發一個input的事件,然後angular就會執行的$digest迴圈,直到模型穩定下來.我們不用手動設定任何事件監聽來同步更新檢視和模型,這樣很贊,這就是angular的目的.
然而,由於每次鍵盤按下,都會觸發$digest迴圈,所以當你在輸入input內容的時候,angular不得不處理所有繫結在scope上的watch監聽,所以,它執行的效率就取決於你在scope上繫結了多少watch監聽,以及這些監聽的回撥函式是怎樣的,這個代價是十分昂貴的.
所以,如果我們能夠自己控制$digest的觸發,比如當使用者停止輸入300毫秒後觸發,又或者是當input元素失去焦點的時候再觸發,那不是更好麼? 於是,angular-1.3的ng-model-options就為我們做了這件事.
一. 通過 updataOn 指定同步ng-model的時間
ng-model-options 提供了一系列的選項去控制ng-model的更新.
通過updateOn引數,我們可以定義input觸發$digest的事件.舉個栗子,我們希望當input失去焦點的時候更新模型,我們只需要按照如下的配置來實現:
eg-1.0
核心程式碼:
<input type="text" ng-model="name" ng-model-options="{ updateOn: 'blur' }"/> <p>Hello {{name}}!</p>
在eg-0.0的基礎上,我們新增了 ng-model-options="{ updateOn: 'blur' }" 這樣一個指令.它告訴angular, 它應該在input觸發了onblur事件的時候再更新ng-model,而不是每次按下鍵盤就立即更新model. 點選檢視效果:(maybe需要牆~~~)
http://plnkr.co/edit/URMCoON9qDFnxdlyiDSS?p=preview
如果我們想要保留預設的更新模型事件,另外再給它新增其它觸發$digest的事件,我們可以使用一個特殊的事件:default. 可以通過空格分隔的字串來給它新增多個事件. 下面這段程式碼能夠在輸入的時候同步更新模型,並且當input失去焦點的時候,也更新模型.
eg-1.1
核心程式碼:
<input type="text" ng-model="name" ng-model-options="{ updateOn: 'default blur' }"/> <p>Hello {{name}}!</p>
點選檢視效果:(maybe需要牆~~~)
http://plnkr.co/edit/6VtaJrCIuO5ePfoz8UXA?p=preview
(效果其實不太看不出來的...因為雖然blur的時候它在同步,但是其實輸入的時候已經同步完了)
好了,現在我們知道指定更新model的事件是怎麼做的了. 接下來讓我們看看怎麼指定更新的延遲時間.
二. 通過 debounce 延遲模型更新
我們可以通過ng-model-options來延遲模型的更新,以此來降低當使用者和模型互動時觸發的$digest迴圈的次數. 這不僅減少了$digest迴圈的次數,同時也是處理非同步資料模型時提升使用者體驗度的一個好方法.
想象有這樣一個元素: input[type="search"] ,每當使用者正在輸入的時候,資料模型就會更新,並且用最新的欄位向後臺提交請求.這樣是沒錯的.然而,我們很可能並不想讓使用者每次按鍵的時候就立刻更新模型,而是希望當使用者輸入完了一段有意義的搜尋欄位以後才更新模型. 在這種情況下,我們正適合使用ng-model-options的 debounce引數. debounce定義了模型更新的延遲毫秒數(需要是整數). 比如剛才提到的這種情況,我們希望當使用者停止輸入1000毫秒以後再更新模型,(停止輸入1000毫秒差不多應該就是輸入完了一段有意義的內容了吧).我們可以像下面這樣,定義debounce引數的值為1000:
eg-2.0:
核心程式碼:
<input type="search" ng-model="searchQuery" ng-model-options="{debounce:1000}"> <p>Search results for: {{searchQuery}}</p>
現在,當輸入搜尋內容的時候,會有1秒的延遲. 點選檢視效果:(maybe需要牆~~~)
http://plnkr.co/edit/lpFwWsTvZxMGfHFe38Bk?p=preview
我們還可以做更多的配置: 為指定的事件指定延遲時間. 為不同的事件指定不同的延時,可以通過給debounce屬性定義一個json物件來實現: 屬性名代表事件名,屬性值代表延遲時間.如果某個事件不需要延遲,那麼它的屬性值就是0.
下面這個栗子實現了這樣的模型: 當使用者在input裡輸入的時候,延遲1000毫秒更新模型,但是當input元素失去焦點的時候,立刻更新模型:
eg-2.1:
核心程式碼:
<input type="search" ng-model="searchQuery" ng-model-options="{updateOn:'default blur',debounce:{default:1000,blur:0}}"> <p>Search results for: {{searchQuery}}</p>
點選檢視效果:(maybe需要牆~~~)
http://plnkr.co/edit/yYsfcjW8KVt9ZESQUSB2?p=preview
三. 通過 $rollbackViewValue方法 同步模型和檢視
由於我們通過ng-model-options來控制了模型的更新時間,所有,在很多時候,模型和檢視就會出現不同步的情況. 舉個栗子,我們配置ng-model-options,讓input在失去焦點的時候同步資料模型,當使用者正在輸入內容時,資料模型沒有發生更新,所以input的value指向的是模型裡真實的值(但是檢視上看到的是使用者輸入的值)
假設在這種情境下,你希望在資料模型更新前把檢視上的值回滾到它真實的值.這時,angular提供了一個叫做$rollbackViewValue的方法來為我們同步資料模型到檢視. 這個方法會把資料模型的值返回給檢視,同時取消所有的將要發生的延遲同步更新事件.
為了解釋這個方法,我寫了兩個demo來感受一下:
eg-3.0
核心程式碼:
<div class="container" ng-controller="Rollback"> <form role="form" name="myForm2" ng-model-options="{ updateOn: 'blur' }"> <div class="form-group"> <label>執行了 $rollbackViewValue() 方法</label> <input name="myInput1" ng-model="myValue1" class="form-control" ng-keydown="resetWithRollback($event)"> <blockquote> <footer>myValue1: "{{ myValue1 }}"</footer> </blockquote> <p></p> </div> <div class="form-group"> <label>沒有執行了 $rollbackViewValue() 方法</label> <input name="myInput2" ng-model="myValue2" class="form-control" ng-keydown="resetWithoutRollback($event)"> <blockquote> <footer>myValue2: "{{ myValue2 }}"</footer> </blockquote> </div> </form> </div>
app.controller('Rollback',function($scope){ $scope.resetWithRollback = function(e){ if(e.keyCode == 27) { $scope.myForm2.myInput1.$rollbackViewValue(); } }; $scope.resetWithoutRollback = function(e){ if(e.keyCode == 27){ angular.noop() } } });
點選檢視效果:(maybe需要牆~~~) http://plnkr.co/edit/iMY8IqH5f8NLuIAxY8zN?p=preview
按照下圖所示的順序操作:
myValue1使用了$rollbackViewValue()方法,可以回滾文字域裡的值和資料模型同步,但是myValue2是不能的.
看一遍這個demo,也就知道了$rollbackViewValue()方法的意思和作用了.
*需要特別注意的一點是,在使用了ng-model-options這種情況下,如果直接修改模型值,有時可能讓檢視同步,有時卻不能,什麼意思,看這個栗子:
eg-3.1:
html部分同eg-3.0.
app.controller('Rollback',function($scope){ $scope.resetWithRollback = function(e){ if(e.keyCode == 27) { $scope.myValue1 = ''; //使用了$rollbackViewValue,總是可以同步檢視,清空myValue1值 $scope.myForm2.myInput1.$rollbackViewValue(); } }; $scope.resetWithoutRollback = function(e){ if(e.keyCode == 27){ //並不是每次都可以成功的同步的,有時可以,有時不可以. $scope.myValue2 = '' } } });
點選檢視執行效果:(maybe需要牆~~~) http://plnkr.co/edit/vve2Xh7LROQLQFa6FFrn?p=preview
在eg-3.0的基礎上,我們做了一個小修改,就是按Esc的時候,不是直接回滾檢視值到當前的資料模型,而是先設定資料模型為空,然後再回滾檢視值.而myValue2,直接設定資料模型為空,不使用回滾.
在demo裡多試幾次就會發現,在這種情況下,在myValue2的input裡按Esc,有時可以同步檢視值為空,有時則不能.
所以,在用了ng-model-opitons的時候,如果在模型沒有被檢視同步之前需要讓檢視被模型同步,不能簡單通過設定模型,必須使用$rollbackViewValue()方法.
翻譯參考:EXPLORING ANGULAR 1.3 - NG-MODEL-OPTIONS 以及 https://docs.angularjs.org/api/ng/directive/ngModelOptions (要牆哦~~~)