angular指令中使用ngModelController

在這篇文章中 angular學習筆記(三十)-指令(10)-require和controller 說到了經過require屬性和controller參數來讓指令與指令之間互相交互.css

本篇主要介紹的是指令與ngModel指令的交互.也就是說,ngModel指令雖然是內置的,但它也有本身的controller屬性,其它指令也能夠經過require來獲得ngModel指令的controller屬性的實例來與ngModel指令進行交互.html

ngModelController用在什麼場合呢?咱們知道,ngModel提供了數據綁定,驗證,樣式更新,數據格式化,編譯功能,可是它故意沒有提供和邏輯相關的處理,好比視圖的從新渲染和監聽dom事件.這些和邏輯處理相關的dom,就應該使用ngModelController來進行數據綁定.git

ngModelController的方法和屬性不少...沒法一一列舉,有些也不多用,這裏會重點講一下經常使用的幾個(帶有*的):angularjs

 

方法:github

*1. $render()json

這個方法會在視圖須要被更新的時候調用. 好比如下這些場景:bootstrap

  • $rollbackViewValue() 被調用. 若是咱們把視圖值回滾到數據模型的值時,$render()會被調用.關於$rollbackViewValue()這個方法,詳見此文:angular-1.3 之ng-model-options指令
  • ng-model綁定的值被程序改變了,而且$modelValue和$viewValue都和上一次不一樣了.

因爲ng-model沒有深度對比模型的變化.什麼叫沒有深度對比模型的變化: 也就是angular學習筆記(十四)-$watch(1)這裏提到的第三個參數ifDeep,ng-model內置的對比機制,至關於這裏的ifDeep是false,不進行深度對比.因此$render()只在$modelValue和$viewValue都發生了實際的改變, 纔會會被調用.什麼叫實際的改變? 就是說,若是$modelValue或者$viewValue是一個對象,而不是一個字符串或者數字,那麼,這個對象中的一個屬性值發生了變化,這不算真正的變化,由於它對象的引用地址沒有發生變化,它指向的仍是同一個對象.api

 

*2. $isEmpty(value) 數組

當咱們須要判斷input的value值是否爲空的時候,可使用這個方法.promise

value是必需要傳的,注意它判斷的是value值是否爲空,而不是ngModel綁定的那個數據模型的值.其實能夠就當它是個判斷是否爲空的方法,傳入一個參數,判斷這個參數是否爲空,你傳入任何值均可以.只是說,通常狀況下都會把ngModel綁定的那個值傳給它.

你能夠本身在指令裏重寫這個方法,來定義本身所須要的'是否爲空'的概念.好比用在一個類型爲checkbox的input元素上,由於當checkbox的值爲false的時候,$isEmpty()的結果就是empty.

若是值是undefined,null,'',或者NaN,則返回true,不然返回false. 

 

3. $setValidity(validationErrorKey, isValid);

 

4. $setPristine()

把元素設置到原始狀態.移除元素的ng-dirty類名,添加ng-pristine類名.

 

5. $setDirty()  

把元素設置到髒值模式.移除元素的ng-pristine類型,添加ng-dirty類名。

 

6.$setUntouched()

把元素設置到沒有觸碰過的狀態.移除ng-touched類名.添加ng-untouched類名.

 

7.$setTouched()

把元素設置到觸碰過的狀態.移除ng-untouched類名,添加ng-touched類名.

 

8.$rollbackViewValue()

參考:angular-1.3 之ng-model-options指令

 

9.$validate()

 

10.$commitViewValue()

把一個未發生的更新提交給$modelValue.

在使用ng-model-options指令的時候,input元素可能正在等待某個事件的觸發,來同步一個將要發生的更新.這個方法不多用,由於ngModelController一般在事件響應中自動處理了這件事.

 

*11.$setViewValue(value, trigger)

更新視圖值.當一個input的指令元素想要改變視圖值的時候,這個方法會被調用.這一般是dom元素內部的事件來處理的.最典型的例子就是在input中輸入值,會改變Hello後面的視圖的值.緣由就是input的輸入事件會調用$setViewValue方法.相似的還有select元素.

若是value是一個對象,而不是一個字符串或者數值,那咱們應該在傳入$setViewValue以前先拷貝一份.由於ngModel不會深度監測對象的變化,它只看對象的引用地址是否發生了變化.若是你僅僅改變了對象的某個屬性,ngModel不會意識到它已經改變了,也不會去通過$parsers和$validators管道.

所以,當對象被傳入到$setViewValue函數裏之後,你不能再改變它的屬性值,不然可能引發當前scope下的模型值被錯誤地改變.

當$setViewValue被調用時,新的value將會經過$parsers和$validators管道後被提交. 若是沒有配置ngModelOptions,那麼value直接進入處理流程,最後它被應用到$modelValue和ng-model綁定的屬性表達式上.

還有一點,全部添加在$viewChangeListeners這個數組裏的函數,都會被執行.

在使用了ngModelOptions的狀況下,上面的說法不適用.上面說到的這些行爲都會被等待直到dom元素的updateOn事件觸發.一樣,若是定義了debounce延遲,那麼這些行爲也會在延遲時間到了之後才發生.

須要注意,執行$setViewValue()方法,不會觸發$digest.

這裏的trigger是作什麼的,不太明白...

 

屬性:

*1.$viewValue

指令元素的視圖中實際的值.注意,它必定等於 $setViewValue(value)的value值

 

*2.$modelValue

ngModel綁定的數據模型的模型值.它不必定等於 $setViewValue(value)的value值,在$setViewValue(value, trigger)裏面提到的使用了ngModelOptions時,好比雖然調用了$setViewValue,可是由於設置了ngModelOptions的debounce屬性,因此它會延遲,等到同步的時候,value值纔會被設置到$modelValue上.

 

3.$parsers

一個數組.數組裏的元素是函數. $setViewValue(value)被賦值給$modelValue以前,value值首先會通過$parsers裏的全部函數,每次將返回值傳遞給下一個函數.最後才被賦值到$modelValue.在這個過程當中就包括了驗證和轉換.對於驗證這個步驟,它會使用$setValidity這個方法,驗證失敗的將返回undefined.

 

4.$formatters

一個數組.數組裏的元素是函數. 和$parsers同樣,它也是管道.當模型值發生變化的時候被調用.模型值會倒着調用數組中的函數,而後把返回值傳給下一個函數,最後返回的值就會被傳遞給dom元素.用來在視圖中格式化模型值:

一個將小寫轉換爲大寫的格式化方法:

function formatter(value) {
  if (value) {
    return value.toUpperCase();
  }
}
ngModel.$formatters.push(formatter);

 

*5. $validators

一個json對象.

{
   validateName: function(modelValue,viewValue){
       return ...
   }
}

當$setViewValue(value)被賦值給$modelValue以前,會通過$parsers管道,通過$parsers管道時,就會通過這個$validators管道.其中validateName是驗證的名字,函數是這個驗證的方法,其中的參數modelValue和viewValue就是$modelValue和$viewValue,若是返回值是true,則經過validateName

驗證,若是返回值是false,則沒有經過validateName驗證,若是沒有經過validateName驗證,$error.validateName就會爲true.這就是angular內部驗證表單項的原理.

eg: 自定義一個驗證規則,輸入內容中必須包含數字

<div class="alert alert-danger" role="alert" ng-show="myForm.myWidget.$error.validCharacters">
    <strong>Oh!</strong> 不符合自定義的驗證規則!
</div>
ngModel.$validators.validCharacters = function(modelValue, viewValue) {
    var value = modelValue || viewValue;
    return /[0-9]+/.test(value);
};

 

*6.$asyncValidators

一個json對象.用來處理異步驗證(好比一個http請求). 

{
   validateName: function(modelValue,viewValue){
       return promise
   }
}

其中validateName是驗證的名字,函數是這個驗證的方法,其中的參數modelValue和viewValue就是$modelValue和$viewValue,返回值必須是一個promise對象,若是這個promise對象傳遞給它下一個.then方法失敗通知,則不經過validateName驗證,若是這個promise對象傳遞給它下一個.then方法成功通知,則表示經過validateName驗證.當異步驗證開始執行的時候,全部的異步驗證都是平行併發的.只有當全部的驗證都經過時,數據模型纔會被同步更新.只要有一個異步驗證沒有完成,這個驗證名就會被放到ngModelController的$pending屬性中.另外,全部的異步驗證都只會在全部的同步驗證經過之後纔開始.

核心代碼:

<input validate-name type="text" name="myWidget" ng-model="userContent" ng-model-options="{updateOn:'blur'}" class="form-control" required uniqueUsername>

<div class="alert alert-danger" role="alert" ng-show="myForm.myWidget.$error.uniqueUsername">
      <strong>Oh!</strong> 已經存在的用戶名!
</div>
app.directive('validateName',function($http,$q){
    return {
        restrict:'A',
        require:'?^ngModel',
        link:function(scope,iele,iattr,ctrl){
            ctrl.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
                var value = modelValue || viewValue;
                // 異步驗證用戶名是否已經存在
                return $http.get('/api/users/' + value).
                then(function resolved(res) {
                    if(res.data){
                        //用戶名已經存在,驗證失敗,給下一個promise傳遞失敗通知.
                        return $q.reject('res.data');
                    }
                    else {
                        //用戶名不存在,驗證成功.
                        return true
                    }

                }, function rejected() {
                        //請求失敗
                })
            };
        }
    }
});

異步驗證比較重要,因此我會另外開一篇文章來舉例詳解:angular中的表單數據異步驗證

 

7.$viewChangeListeners

一個數組,數組中的元素都是函數.這些函數在視圖發生改變的時候被執行,不帶有什麼參數,也不須要返回值.在第11條方法裏說到,在不使用ngModelOptions延遲時調用$setViewValue的時候,他們就會執行.

 

*8.$error

json對象. 這個很簡單,用到不少次了.就是全部驗證失敗的驗證名和失敗信息組成的json對象.

 

*9.$pending

json對象. 第6個屬性裏提到過的,正在進行中的異步驗證會被放在這個對象裏

 

10.$untouched

布爾值.若是元素尚未失去過焦點,那這個值就是true.

 

11.$touched

布爾值.若是元素已經失去過焦點,那這個值就是false. 

 

12.$pristine

布爾值.若是元素尚未和用戶發生過交互,那這個值就是true.

 

13.$dirty

布爾值.若是元素已經和用戶發生過交互,那這個值就是true.

 

*14.$valid

布爾值.這個也很經常使用,就是當全部驗證(異步同步),都經過的時候,它就是true

 

*15.$invalid

布爾值.這個也很經常使用,就是當全部驗證(異步同步),其中有一個或一個以上驗證失敗,它就是true.

 

16.$name

字符串.很簡單,就是獲取元素的name屬性.

 

注意上面說到的這些屬性:

咱們這篇文章說的是ngModelController,因此說這些屬性是ngModelController的屬性,可是其中有一部分通常不在ngModelController裏面用,好比:$error,$pending,$valid,$invalid,等,這些屬性,咱們一般是這樣用的:

    <div class="alert alert-danger" role="alert" ng-show="myForm.myWidget.$error.uniqueUsername">
      <strong>Oh!</strong> 已經存在的用戶名!
    </div>
    <div class="panel-body">
      {{myForm.myWidget.$pending}}
    </div>

可是,你內心要知道,其實他們也是ngModelController的屬性哦~

ngModelController就所有講完了,可能看起來比較混亂...我儘可能按照理解的總結一下:

兩個核心的屬性:

  $viewValue: 視圖裏的值,也就是input輸入框的值,這個值就是$setViewValue(value)中的value.

  $modeValue: 數據模型的值

     $viewValue會在input事件觸發的時候,被同步到$modelValue.什麼叫input事件觸發? 若是我什麼都沒有定義,那麼,就是ng默認的事件,也就是一邊輸入,就會一邊觸發.若是是定義了ngModelOptions,那就是在本身指定的事件觸發的時候,$viewValue被同步到$modelValue.

     $viewValue被同步到$modelValue時,並非直接就賦值了,而是通過了下面說到的三個核心管道.

     雙向數據綁定的那個表達式,好比ng-model='name', 這個scope下的name值,它是和$modelValue保持一致的.因此,當input事件觸發的時候,$modelValue被賦值,name也就在這時被改變爲這個值.

兩個核心方法: 

  $render: 若是模型值被改變,須要同步視圖的值(後臺改變了模型值,或者使用了$rollbackViewValue()).也就是說,$render函數負責將模型值同步到視圖上.

  $setViewValue: 用於設置視圖值,也就是將input的value值賦值給$viewValue.

     須要注意: $render是同步模型值到視圖值,那麼同步視圖值到模型值是什麼方法呢? 注意angular並無爲咱們提供這樣一個接口.而是在兩個核心屬性裏面提到的,當input事件觸發時候,就會把視圖值同步到模型值,可是咱們能夠自定義視圖值同步到模型值的過程當中的三個管道.

三個核心管道: 

  $parsers: 用於改變視圖值的格式.

  $validators: 用於添加自定義的同步驗證.

  $asyncValidators: 用於添加自定義的異步驗證.

     這個三個管道具體怎麼用,看例子.

四個經常使用屬性:

  $error: 用來存儲驗證錯誤

  $pending: 用來存儲正在異步驗證中的驗證內容

  $valid: 用來存儲表單項是否都經過了驗證.

  $invalid: 用來存儲表單項是否都經過了驗證.

     這些都是用在html裏面,使用myFrom.myWidget...來獲取的...

最後我用一個例子來把這個流程給順一遍:

'請輸入內容'這個文本框實際上是個div,可編輯的的div,而後咱們經過ngModelController來讓它實現和input同樣的雙向數據綁定的效果.

爲了清楚的看到效果,我經過ngModelOptions給它添加了1000毫秒的延遲.

另外,把輸入的內容經過$parsers屬性來進行格式轉換,把小寫的轉換爲大寫.

下面來看代碼:

html:

<!DOCTYPE html>
<html ng-app="customControl">
<head>
  <title>ngModelController</title>
  <meta charset="utf-8">
  <script src="../angular-1.3.2.js"></script>
  <script src="script.js"></script>
  <link type="text/css" href="../bootstrap.css" rel="stylesheet" />
  <style>
    *{font-family: 'MICROSOFT YAHEI'}
  </style>
</head>
<body>
<div class="container" ng-controller="ctrl">
  <div class="page-header">
    <h1>ngModelController- <small>建立一個實現了雙向數據綁定的可編輯文本區域</small></h1>
  </div>
  <form role="form" name="myForm">
    <div class="form-group">
      <div contenteditable name="myWidget" ng-model="userContent" ng-model-options="{debounce:1000}" class="form-control" required default-text="請輸入內容"></div>
    </div>
    <div class="form-group">
      <button type="button" class="btn btn-default btn-primary" ng-click="setNone()">設置爲'抱歉,我沒有想輸入的內容'</button>
    </div>
    <div class="alert alert-danger" role="alert" ng-show="myForm.myWidget.$error.required">
      <strong>Oh!</strong> 必填!
    </div>
  </form>
  <div class="panel panel-primary">
    <div class="panel-heading">
      <h3 class="panel-title">用戶輸入的內容爲:</h3>
    </div>
    <div class="panel-body">
      {{userContent}}
    </div>
  </div>
</div>

</body>
</html>

它和普通的雙向數據綁定的惟一區別就是,它是一個可編輯div.

而後咱們看angularjs是如何處理contenteditable指令的:

var app = angular.module('customControl',[]);
app.controller('ctrl',function($scope){
    $scope.setNone = function(){
        $scope.userContent = '抱歉,我沒有想輸入的內容'
    }
});
app.directive('contenteditable',function(){
    return {
        restrict:'A',
        require:'?^ngModel',
        link:function(scope,element,attrs,ngModel){
            if(!ngModel){
                return
            }
            //一開始scope.userContent是空
 console.log(ngModel.$isEmpty(scope.userContent));
            ngModel.$setViewValue(attrs.defaultText);    //這裏其實不須要調用的,只是爲了演示$isEmpty,不是demo須要 //調用了$setViewValue之後就不爲空了,可是若是設置了ngModelOptions,則沒用,由於$setViewValue沒有被賦值給$modelValue.
 console.log(ngModel.$isEmpty(scope.userContent));
 ngModel.$render = function(){ element.html(ngModel.$viewValue || attrs.defaultText) };
            element.bind('focus',function(){
                if(element.html()==attrs.defaultText){
                    element.html('')
                }
            });
            element.bind('focus blur keyup change',function(){
                console.log(scope.userContent);
                ngModel.$setViewValue(element.html());
                console.log('$viewValue爲:'+ngModel.$viewValue);
                console.log('$modelValue爲:'+ngModel.$modelValue);
            });
 ngModel.$parsers.push(function(value){ return value.toUpperCase() })
        }
    }
});

$isEmpty(value):

這裏把userContent傳入,判斷它是否爲空,若是這裏沒有使用ngModelOptions,那麼在調用了$setViewValue之後,userContent就會有值了.可是這裏使用了ngModelOptions,因此$setViewValue之後,$viewValue值不會立刻被賦值給$modelValue,而模型值應該是等於$modelValue的,

因此這裏獲得的兩次結果都是true.

$render():

當模型值變化的時候,這個方法會被調用,也就是當我點擊  "設置爲'抱歉,我沒有想輸入的內容'"  按鈕的時候,userContent發生了變化,會調用$render()方法.注意一點,當直接改變userContent的值的時候,$viewValue和$modelValue都會被異步的改變爲這個值.改變之後,再調用$render().

$setViewValue(): 

當用戶輸入的時候,經過$setViewValue改變$viewValue的值, 默認的input ng-model它本身處理了這件事,這裏div元素咱們手動處理.

$viewValue和$modelValue和綁定值:

這裏我ngModelOptions設置延遲了1000毫秒,當我很慢很慢的輸入時,結果以下:

能夠看到,當尚未開始輸入時,$viewValue是'',由於div裏面的內容就是'',而$modelValue是undefined

當我開始輸第一個字,$viewValue會馬上變成我輸入的內容(這是$setViewValue的做用),可是$modelValue不會發生變化

當我延遲了1000毫秒之後,再輸入下一個字,$viewValue固然實時同步了,而能夠看到,$modelValue也同步了上一次輸入的值.由於已通過了1000毫秒了.

...

值得注意的是,userContent始終是和$modelValue一致的.或者說$modelValue是和userContent一致的.我也不清楚是誰先變化.但能夠知道他倆是一致的.

最後延遲1000毫秒後讓鼠標失去焦點,這樣,三個值是徹底一致的了.

若是輸的快一點,那就是這樣:

$parsers:

這個很簡單,就是讓$viewValue被賦值給$modelValue的時候通過這個管道,把小寫變成了大寫.

 

點擊查看效果: http://plnkr.co/edit/CbOS1nFosPDfQXvsGTyR?p=preview

 

相關閱讀:

ngModelOptionsng(包含ngModel中的$rollbackViewValue方法):

angular-1.3 之ng-model-options指令

ngModel自定義驗證:

angular中的表單數據自定義驗證

 

 

參考文獻: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController  

完整代碼: https://github.com/OOP-Code-Bunny/angular/tree/master/ngModelController

相關文章
相關標籤/搜索