文檔地址:https://docs.angularjs.org/api/ng/type/ngModel.NgModelControllerhtml
首先聲明:DOM value 與view value是徹底不一樣的概念,不能保證二者的值是相等的。angularjs
ngModelController是爲ng-model指令提供的API。編程
這個控制器包含了爲數據綁定(data-binding)、校驗(validation)、CSS更新(CSS updates)、值的格式化和解析(value formatting and parsing)等功能。它不包含任何處理DOM渲染或者DOM事件監聽的邏輯。這些與DOM相關的邏輯應該由那些使用ngModelController進行數據綁定來控制元素的指令提供。api
這個例子演示了一個自定義控件怎麼使用ngModelController實現數據綁定。數組
注意不一樣指令(contenteditable、ng-model、required)之間是怎麼共同合做實現了預想中的結果。promise
contenteditable是一個HTML5屬性,告訴瀏覽器,元素的內容能夠被用戶編輯。瀏覽器
咱們使用$sce服務和$sanitize服務來自動移除」bad」內容,好比行內事件監聽器(e.g. ‘<span onclick=」…」>’)。服務器
However, as we are using $sce the model can still decide to provide unsafe content if it marks that content using the $sce service.異步
CSS:async
[contenteditable] { border: 1px solid black; background-color: white; min-height: 20px; } .ng-invalid { border: 1px solid red; }
關於ngSanitize:angular-ngSanitize模塊-$sanitize服務詳解
JS:
angular.module('customControl', ['ngSanitize']).directive('contenteditable', ['$sce', function($sce) { return { restrict: 'A', // only activate on element attribute require: '?ngModel', // get a hold of NgModelController link: function(scope, element, attrs, ngModel) { if (!ngModel) return; // do nothing if no ng-model // Specify how UI should be updated ngModel.$render = function() { element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); }; // Listen for change events to enable binding element.on('blur keyup change', function() { scope.$evalAsync(read); }); read(); // initialize // Write data to the model function read() { var html = element.html(); // When we clear the content editable the browser leaves a <br> behind // If strip-br attribute is provided then we strip this out if (attrs.stripBr && html === '<br>') { html = ''; } ngModel.$setViewValue(html); } } }; }]);
HTML:
<form name="myForm"> <div contenteditable name="myWidget" ng-model="userContent" strip-br="true" required>Change me!</div> <span ng-show="myForm.myWidget.$error.required">Required!</span> <hr> <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea>
</form>
控件視圖中的實際值。對於input元素,這個值是字符串。(參考$setViewValue瞭解何時設置$viewValue的值)
控件所綁定的模型中的值。
一個函數組成的數組。做爲一個管道(pipeline),每當控件用一個來自DOM的新的$viewValue(一般是經過用戶輸入)更新ngModelcontroller時執行$parsers中的函數。參考$setViewValue瞭解詳細的生命週期說明。
注意,當綁定到ngModel的表達式是經過編程的方式改變時,$parses中的函數不會被調用。
這些函數按照在數組中的順序被調用,前一個函數的返回值傳遞給下一個。最後一個函數的返回值被轉發給$validators集合(驗證器集合)。
解析器($parsers)用於消除或者轉換$viewValue的值。
若是從一個解析器($parsers中的某一個函數)返回了undefined,那就意味着出現了一個解析錯誤。這種狀況下,沒有$validators會運行。並且,除非ngModelOptions.allowInvalid被設置爲true,不然ngModel將會被設置爲undefined。這個解析錯誤被存儲在ngModel.$error.parse中。
下面是一個解析器將輸入值(input value,或者說view value)轉換爲小寫的例子:
function parse(value) { if(value) { return value.toLowerCase(); } } ngModelController.$parsers.push(parse);
一個函數組成的數組。做爲一個管道,每當綁定到ngModel上的表達式的值經過編程的方式改變時,數組中的函數將會被執行。當控件中的值是經過用戶交互改變的時候,$formatters中的函數將不會被執行。
$formatters用於在控件(模型)中格式化或者轉換$modelValue。
$formatters中的函數按照在數組中的倒序被調用,每個函數執行後的返回值會傳遞給下一個函數。最後一個函數的返回值被做爲最終的DOM值(DOM value)使用。
下面是一個formatter(不知道怎麼翻譯)將model value轉換爲大寫的例子:
function format(value) { if(value) { return value.toUpperCase(); } } ngModelController.$formatters.push(format);
一個驗證器(validators)的集合(至關於一個字面量對象),當model value須要被更新時會被應用。這個對象的鍵值(key value)是驗證器(validator,是一個函數,能夠進行相應的驗證運算)的名字。model value傳入驗證器,做爲驗證器的一個參數。驗證器根據驗證結果返回一個Boolean值。
例子
NgModelcontroller.$fvalidators.validCharacters = function(modelValue,viewValue) { var value = modelValue || viewValue; return /[0-9]+/.test(value) && /[a-z]+/.test(value) && /[A-Z]+/.test(value) && /\W+/.test(value); };
異步驗證。一個校驗的集合,用來進行異步驗證(e.g. 一個HTTP請求 )。
當校驗函數在模型校驗期間(model validation process)運行時,校驗函數會返回一個promise。一旦這個promise被delivered,當知足校驗時,那麼校驗的狀態(validation status)將會被設爲true;當不知足時,被設爲false。
當異步校驗器(asynchronous validators)被觸發時,每個校驗器都會並行運行,而且只有當全部校驗器都知足後,model value纔會被更新。只要任一校驗器沒有被知足,那麼對應的鍵(key,能夠理解爲這個校驗器的名字)將會被添加到ngModelcontroller的$pending屬性中。
固然,只有當全部的同步校驗器(synchronous validators)都校驗經過以後,全部的異步校驗器纔會運行。
注意:若是使用了$http服務,那麼爲了校驗經過,服務器返回一個成功的HTTP響應狀態碼是很是重要的。爲了使其不知足校驗,那麼就返回‘4xx’狀態碼。
例子
ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { var value = modelValue || viewValue; // Lookup user by username return $http.get('/api/users/' + value). then(function resolved() { //username exists, this means validation fails return $q.reject('exists'); }, function rejected() { //username does not exist, therefore this validation passes return true; }); };
一個函數組成的數組。當view value發生變化時,其中的函數將被執行。函數被調用的時候沒有參數,並且函數的返回值也會被忽略。這能夠被用於代替額外的用於監聽model value的$watches。
An object hash with all failing validator ids as keys.
(若是$validators中的某一個驗證函數返回了false,那麼這個驗證函數的名字就成爲$error的一個鍵,這個鍵被賦值爲true)。
其餘的一些屬性:
$pending – object -- An object hash with all pending(待定) validator ids as keys(validator ids是驗證函數的名字).
$untouched -- boolean -- True if control has not lost focus yet.
$touched -- boolean -- True if control has lost focus.
$pristine -- boolean -- True if user has not interacted with the control yet.
$dirty -- boolean -- True if user has already interacted with the control.
$valid -- boolean -- True if there is no error.
$invalid -- boolean -- True if at least one error on the control.
$name -- string -- The name attribute of the control.
當視圖(view)須要被更新時調用。使用ng-model的那個指令負責實現這個方法。
注意:$render函數的內部邏輯是須要開發者根據本身的需求本身實現的,其本質上的做用是將DOM value 同步爲 view value -- $viewValue。
在下列情形時,$render()方法纔會被調用:
由於ng-model不進行深檢測(watch),$render()只在$modelValue和$viewValue的值確實與以前的值不一樣時才調用。若是$modelValue或者$viewValue的值爲對象(object)而不是字符串(string)或者數字(number),而且你只更改了這個對象的一個屬性的值時,那麼$render()將不會被調用。
當咱們要判斷一個輸入值是否爲空時,能夠調用這個函數。
好比,有些指令根據輸入的值是否爲空而決定是否執行。
默認狀況下,$isEmpty函數檢測輸入值是不是‘undefined’、‘’’’、‘null’或者‘NaN’。
對於輸入指令,空值的概念可能與默認定義的那些不一樣,此時你能夠重寫這個方法。指令‘checkboxInputType’就是這麼作的,由於對於這個指令來講‘false’意味着空值。
這個函數的參數是要檢測的那個輸入的值。
該函數返回一個boolean值,true意味着檢測的那個值是空。
把控件重置到它最初的狀態(pristine state)。
調用這個方法用於移除控件class中的‘ng-dirty’而且把這個控件重置到最初的狀態(添加‘ng-pristine’class)。當一個控件自從被第一次編譯以後沒有再被更改,那麼能夠認爲這個模塊(原文中用的是model)是最初的狀態。
將控件設置爲髒狀態(dirty state)。
調用這個方法用於移除控件的‘ng-pristine’class,並把這個控件設置爲髒狀態(添加‘ng-dity’class)。若是一個控件自從被第一次編譯以後被更改,那麼能夠認爲這個模型(原文中用的是model)處於髒狀態。
把控件設置爲untouched state。
調用這個方法用於移除控件的‘ng-touched’class,而且把這個控件設置爲untouched(添加‘ng-untouched’class)。在編譯時,一個模型默認被設爲untouched。然而,若是這個模型已經被用戶touched,這個函數能夠被用來恢復模型的狀態。
把控件設置爲touched state。
調用這個方法用於移除‘ng-untouched’class,而且把這個控件設置爲touched state(添加‘ng-touched’class)。若是用戶第一次將焦點定位到了控件元素上,而後又將焦點從控件上移開,那麼就認爲這個模型已經被touched。
取消更新(update)而且重置(reset)輸入元素的值,阻止對$modelValue更新。
這個方法會把數據模型的值返回給視圖,同時取消全部的將要發生的延遲更新事件。
若是有一個輸入控件使用了ng-model-options,而且給ng-model-options設置了去抖動更新(debounced updates)或者設置了基於特殊事件的更新(好比blur事件),那麼就存在一段時期,$viewValue與$modelValue不是同步的。
在這種狀況下,你能夠用$rollbackViewValue()手動取消這種去抖動更新或者說基於事件的更新(debounced / future update),而且重置這個輸入(控件的)值爲最後提交的那個視圖值(view value)(其實是將視圖值與當前的$modelView同步)。
若是你試圖在這些去抖動更新函數執行以前或者基於事件更新的事件發生以前(before these debounced/future events have resolved/occurred)經過編程的方式更新ngModel指令的$modelValue的值,那麼你可能遇到困難。由於AngularJS的髒檢測機制不能分清數據模型到底有沒有確實發生改變。
$rollbackViewValue方法應該在經過編程方式改變一個輸入控件的數據模型以前調用,這個控件可能有被掛起的事件(debounced/future events)。這對於確保輸入控件的值能順利地與新的數據模型同步而且同時取消那些掛起的事件是很重要的。
舉例子以前腦海中要先有這麼一個想法:ng-model-options的做用是延遲view value當即同步給model value(能夠經過事件、時間或者自定義的去抖動函數實現);$rollbackViewValue的做用是將當前的model value當即同步給view value,而且取消掛起的延遲同步函數或事件等。並且通常狀況下是用不到$rollbackViewValue方法的,這個方法通常在使用了ng-model-options的情形下才可能用獲得。
例子
這裏要說一句,官方列出的例子容易讓人糊塗,因此這裏對官方的例子作了一些修改,而且參考了網上其餘博主的文章。
咱們都知道,通常狀況下使用ng-model,view value與model value是實時同步更新的。下面這個例子是使用了ng-model-options的情形:
js代碼:
angular.module('cancel-update-example', []).controller('CancelUpdateController', ['$scope', function($scope) { $scope.model = {value1: '', value2: ''}; $scope.setValue = function(e,value) { $scope.model[value] = 'test'; console.log(value+'****************'+$scope.model[value]); }; $scope.setEmpty = function(e, value, rollback) { if (e.keyCode === 27) { console.log(27); console.log('rollback*********'+rollback); e.preventDefault(); if (rollback) { console.log('rollback*********'+rollback); $scope.myForm[value].$rollbackViewValue(); } //$scope.model[value] = ''; } console.log(value+'****************'+$scope.model[value]); }; }]);
HTML代碼:
<div ng-controller="CancelUpdateController"> <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> <div> <p id="inputDescription1">Without $rollbackViewValue():</p> <input name="value1" aria-describedby="inputDescription1" ng-model="model.value1" ng-keydown="setEmpty($event, 'value1')"> value1: "{{ model.value1 }}" </div> <div> <p id="inputDescription2">With $rollbackViewValue():</p> <input name="value2" aria-describedby="inputDescription2" ng-model="model.value2" ng-keydown="setEmpty($event, 'value2', true)"> value2: "{{ model.value2 }}" </div> </form> <button ng-click="setValue($event, 'value1')" type="button">reset value1</button> <button ng-click="setValue($event, 'value2')" type="button">reset value2</button> </div>
ng-model-options=」{uodateOn:’blur’}」的意思是在控件失去焦點的時候將view value同步到model value。
上面例子中,當焦點沒有從輸入控件上移開時,無論怎麼輸入,咱們輸入的值都不會同步到model value。只有當輸入控件失去焦點時,咱們輸入的值纔會同步到model value。
對於value1咱們先在輸入框隨便輸入一些內容,能夠發現輸入框後的value1並無隨着咱們的輸入而當即同步出來,由於咱們用ng-model-options設置了失去焦點後才更新。此時,咱們點擊按鈕經過編程的方式把value1 的model value的值設置爲test,發現model value的更改當即就同步到了view value。以上情形說明ng-model-options只對view value同步到model value起延遲做用,對model value同步到view value不起做用。
對於value2對應的輸入框,給他添加鍵盤按下事件,就是若是按下Esc鍵,就調用$rollbackViewValue方法。咱們先點擊按鈕,經過編程把value2對應的model value更改成test,此時輸入框中的內容也被同步爲了test,而後再在value2對應的輸入框中輸入內容,而後再按下Esc鍵,咱們會發現輸入框裏的內容又變回了test。這是由於雖然咱們在輸入框裏輸入了內容,可是這個內容並無同步到model value,此時的model value仍是原來的test,此時調用$rollbackViewValue,當前的model value當即同步到了view value,因此輸入框中的內容也就變成了test。
下面是$rollbackViewValue在AngularJS中的源碼:
$rollbackViewValue: function() { this.$$timeout.cancel(this.$$pendingDebounce); this.$viewValue = this.$$lastCommittedViewValue; this.$render(); },
運行每個註冊過的驗證器(validators),首先是同步驗證器,而後是異步驗證器。
若是驗證變動無效,除非ngModelOptions的ngModelOptions.allowInvalid的值爲true,不然數據模型的值(model value)將會被設置爲‘undefined’。
若是驗證變動有效,數據模型的值(model value)將會被設置爲最後的那個可用的有效的$modelValue的值,也就是說要麼是最後解析的那個值,要麼是來自做用域的最後設置的那個值。
提交掛起的更新到$modelValue。Commit a pending update to the $modelValue.
值的更新同步可能由於去抖動事件(debounced event)或者經過ng-model-options設置的future event而掛起。這個方法不多使用,由於ngModelController一般會處理對輸入時間的響應。
這個方法用於更新view value。($setViewValue方法用來將view value -- $viewValue 同步爲DOM value)
注意DOM value與view value不是一回事。
當一個控件想要更改view value時,能夠調用這個方法。特別地,這是在DOM事件處理程序中完成的。好比,對於input控件,當輸入值變動時調用這個方法;對於select控件,當某一個option被選中時調用這個方法。
當$setViewValue被調用時,這個新值將會被提交給$parsers和$validators管道。若是沒有特殊的ngModelOptions配置項,那麼這個值會被直接傳遞給$parsers管道。這個過程以後,$validators和$asyncValidators會被調用,而後這個值會被同步給$modelValue。
最後這個值會賦給ng-model屬性指定的表達式,而且調用全部註冊的變動監聽器(change listeners,保存在$viewChangeListeners數組中)。
若是ng-model-options指令指定了updateOn,可是沒有使用default觸發器,那麼全部的動做將會掛起直到updateOn指定的事件在DOM元素上觸發爲止。
若是ng-model-options指令爲特殊事件使用了一個自定義的防抖函數,那麼全部這些動做也會被防抖動處理。
要注意的是,$digest只會在updateOn指定的事件觸發時或者若是debounce字段指定時間結束以後被觸發。
當使用標準的輸入控件時,view value一般是一個字符串(在某些狀況下會被解析爲其餘的類型,好比用於input[date]的Date對象)。然而,自定義控件可能也會把對象傳遞給這個方法,在這種狀況下,咱們應該在把這個對象傳遞給$setViewValue以前將這個對象作一個拷貝。這是由於,ngModel不會對對象執行深檢測,他只會檢查標識符是否發生了改變。若是你僅僅改變了這個對象上的某一個屬性的值,那麼ngModel將會認爲這個對象沒有被改變,從而不會調用$parsers和$validators管道。基於這個緣由,一旦對象被傳給了$setViewValue,你不該該改變其拷貝的屬性。不然,可能致使做用域上的model value發生錯誤的改變。
注意:不管如何(任何狀況下),傳入$setViewValue方法的值應該老是體現控件當前的值。好比,若是你對一個輸入控件調用$setViewValue方法,你應該把輸入控件的DOM value傳給這個方法。不然的話,這個控件和做用域的數據模型(scope model)將不會同步。同時還要注意,$setViewValue方法不會調用$render或者說不會以任何方式更改控件的DOM value。若是咱們想經過編程的方式更改控件的DOM value,咱們應該經過ngModel指令綁定的做用域的表達式進行更改。這個新值將會被model controller(我以爲這個控制器應該指的是ngModelController)獲取,這個控制器會將這個值經過$formatters格式化,經過$render將這個值更新到DOM,最後調用$validate驗證這個值。
$setViewValue方法能夠接收兩個參數,第一個參數是來自視圖的值(我以爲,準確地說應該是DOM value)。第二個參數是一個字符串,是一個事件的名字,經過這個事件觸發更新(設置view value的值),這個參數不是必須的。
做用是經過編程的方式重寫model options的配置。
調用這個方法時,先前的ModelOptions的值不會被修改。取而代之的是建立一個新的ModelOptions對象,這個新建立的對象會重寫或繼承先前的那個ModelOptions對象的值。(同名就重寫)
查看ngModelOptions指令的相關內容瞭解有哪些可配置項以及model option的繼承機制是如何工做的。
注意這個方法不能重寫getterSetter屬性的值。
注意這個函數隻影響在ngModelController上設置的選項,而不是在ngModelOptions指令上的選項,而這些選項多是最初得到的。
這個方法的參數是一個對象字面量。
這個函數基於當前$modeValue在model value --> view value管道中運行。
該方法在如下情形時執行:
一、$modelValue經過$formatters管道而且將獲得的結果設置給$viewValue時;
二、給元素設置ng-empty或ng-not-empty類名時;
三、若是$viewValue被改變時:
$render函數被調用
驗證狀態被設置和$validators被運行
當綁定在做用於上的值改變時,這個方法被ngModel在內部調用。開發者不須要本身調用這個方法。
當$viewValue或者渲染的DOM value沒有被正確的格式化,$modelValue必須再次經過$formatters管道時,可使用這個函數。
這個方法的做用是更改驗證狀態,並通知表單。
這個方法能夠在$parsers / $formatters中或者自定義的驗證工具中調用。
然而,在絕大多數狀況下使用ngModel.$validators和ngModel.$asyncValidators就已經足夠了。ngModel.$validators和ngModel.$asyncValidators會自動調用$setValidity。
這個方法能夠接收兩個參數:
第一個參數是一個字符串,是報錯的驗證器的名字(validationErrorKey Name of the validator)。validationErrorKey將會被賦值給$error[validationErrorKey]或者$pending[validationErrorKey](對於未經過的$asyncValidators),so that it is available for data-binding。
validationErrorKey應該用駝峯命名法,而且會被轉化爲用破折號連接的類名。好比,myError 將會被轉換爲ng-valid-my-error和ng-invalid-my-error類名,綁定的時候能夠這樣綁定{{someForm.someControl.$error.myError}}。
第二個參數是一個布爾值。表示一個驗證狀態,能夠傳入如下狀態:valid(true)、invalid(false)、pending(undefined)、skipped(null)。
Pending用於未經過驗證的$asyncValidators。Skipped被AngularJS用於當解析錯誤而致使驗證器(validators)不運行時或者因爲$validators中任意一個驗證函數未經過而致使$asyncValidators不起做用時。