在這篇文章中 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沒有深度對比模型的變化.什麼叫沒有深度對比模型的變化: 也就是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自定義驗證:
參考文獻: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
完整代碼: https://github.com/OOP-Code-Bunny/angular/tree/master/ngModelController