angular-1.3 之ng-model-options指令

詩&遠方發表於2014-11-18

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

 

相關文章