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 (要牆哦~~~)