由淺入深剖析Angular表單驗(yàn)證
最近手上維護(hù)的組件剩下的BUG都是表單驗(yàn)證,而且公司的表單驗(yàn)證那塊代碼經(jīng)歷的幾代人,里面的邏輯開(kāi)始變得不清晰,而且代碼結(jié)構(gòu)不是很angular。
是很有必要深入了解表單驗(yàn)證。
<body ng-controller="MainController"> <form name="form" novalidate="novalidate"> <input name="text" type="email" ng-model="name"> </form> </body>
ngModel是angular的黑魔法,實(shí)現(xiàn)雙向綁定,當(dāng)name的值變化的時(shí)候,input的value也會(huì)跟著變化。
當(dāng)用戶在input修改value的時(shí)候,name的值也會(huì)跟著變化。
novalidate="novalidate"的目的是去除系統(tǒng)自帶的表單驗(yàn)證。
上面那段代碼解析完,angular會(huì)在MainController的$scope下面生成一個(gè)變量"form",$scope.form,這個(gè)變量的名稱(chēng)跟html中form.name一致。
而$scope.form.text為文本輸入框的Model,繼承自ngModelController。
其中$scope.form實(shí)例自FormController。其內(nèi)容為:
文本輸入框的Model(也就是$scope.form.text)為:
其中$dirty/$pristine,$valid/$invalid,$error為常用屬性。尤其是$error。
最簡(jiǎn)單的表單驗(yàn)證:
了解了form和輸入框,就可以先擼個(gè)最簡(jiǎn)單的顯示錯(cuò)誤的指令。
html內(nèi)容如下:
<form name="form" novalidate="novalidate"> <input name="text" type="email" ng-model="name" error-tip> </form>
指令代碼如下:
// 當(dāng)輸入框出錯(cuò),就顯示錯(cuò)誤 directive("errorTip",function($compile){ return { restrict:"A", require:"ngModel", link:function($scope,$element,$attrs,$ngModel){ //創(chuàng)建子scope var subScope = $scope.$new(), //錯(cuò)誤標(biāo)簽的字符串,有錯(cuò)誤的時(shí)候,顯示錯(cuò)誤內(nèi)容 tip = '<span ng-if="hasError()">{{errors() | json}}</span>'; //臟,而且無(wú)效,當(dāng)然屬于錯(cuò)誤了 $scope.hasError = function(){ return $ngModel.$invalid && $ngModel.$dirty; } //放回ngModel的錯(cuò)誤內(nèi)容,其實(shí)就是一個(gè)對(duì)象{email:true,xxx:true,xxxx:trie} $scope.errors = function(){ return $ngModel.$error; } //編譯錯(cuò)誤的指令,放到輸入框后面 $element.after($compile(tip)(subScope)); } } });
先看看執(zhí)行結(jié)果:
輸入無(wú)效的郵箱地址的時(shí)候:
輸入正確的郵箱地址的時(shí)候:
errorTip指令一開(kāi)始通過(guò) require:"ngModel" 獲取ngModelController。然后創(chuàng)建用于顯示錯(cuò)誤的元素到輸入框。
這里使用了$compile,$compile用于動(dòng)態(tài)編譯顯示html內(nèi)容的。
當(dāng)有錯(cuò)誤內(nèi)容的時(shí)候,錯(cuò)誤的元素就會(huì)顯示。
為什么subScope可以訪問(wèn)hasError和errors方法?
因?yàn)樵玩湣?聪聢D就知道了。
自定義錯(cuò)誤內(nèi)容
好了,很明顯現(xiàn)在的表單驗(yàn)證是不能投入使用的,我們必須自定義顯示的錯(cuò)誤內(nèi)容,而且要顯示的錯(cuò)誤不僅僅只有一個(gè)。
顯示多個(gè)錯(cuò)誤使用ng-repeat即可,也就是把"errorTip"指令中的
tip = '<span ng-if="hasError()">{{errors() | json}}</span>';
改成:
tip = '<ul ng-if="hasError()" ng-repeat="(errorKey,errorValue) in errors()">' + '<span ng-if="errorValue">{{errorKey | errorFilter}}</span>' + '</ul>';
其中errorFilter是一個(gè)過(guò)濾器,用于自定義顯示錯(cuò)誤信息的。過(guò)濾器其實(shí)是個(gè)函數(shù)。
其代碼如下:
.filter("errorFilter",function(){ return function(input){ var errorMessagesMap = { email:"請(qǐng)輸入正確的郵箱地址", xxoo:"少兒不宜" } return errorMessagesMap[input]; } });
結(jié)果如下:
好了,到這里就能夠處理“簡(jiǎn)單”的表單驗(yàn)證了。對(duì),簡(jiǎn)單的。我們還必須繼續(xù)深入。
自定義表單驗(yàn)證!
那我們就來(lái)實(shí)現(xiàn)一個(gè)不能輸入“帥哥”的表單驗(yàn)證吧。
指令如下:
.directive("doNotInputHandsomeBoy",function($compile){ return { restrict:"A", require:"ngModel", link:function($scope,$element,$attrs,$ngModel){ $ngModel.$parsers.push(function(value){ if(value === "帥哥"){ //設(shè)置handsome為無(wú)效,設(shè)置它為無(wú)效之后,$error就會(huì)變成{handsome:true} $ngModel.$setValidity("handsome",false); } return value; }) } } })
結(jié)果如下:
這里有兩個(gè)關(guān)鍵的東西,$ngModel.$parsers和$ngModel.$setValidity.
$ngModel.$parsers是一個(gè)數(shù)組,當(dāng)在輸入框輸入內(nèi)容的時(shí)候,都會(huì)遍歷并執(zhí)行$parsers里面的函數(shù)。
$ngModel.$setValidity("handsome",false);設(shè)置handsome為無(wú)效,會(huì)設(shè)置$ngModel.$error["handsome"] = true;
也會(huì)設(shè)置delete $ngModel.$$success["handsome"],具體可以翻翻源碼。
這里我總結(jié)一下流程。
-->用戶輸入
-->angular執(zhí)行所有$parsers中的函數(shù)
-->遇到$setValidity("xxoo",false);那么就會(huì)把xxoo當(dāng)做一個(gè)key設(shè)置到$ngModel.$error["xxoo"]
-->然后errorTip指令會(huì)ng-repeat $ngModel.$error
-->errorFilter會(huì)對(duì)錯(cuò)誤信息轉(zhuǎn)義
-->最后顯示錯(cuò)誤的信息
自定義輸入框的顯示內(nèi)容
很多時(shí)候開(kāi)發(fā),不是簡(jiǎn)簡(jiǎn)單單驗(yàn)證錯(cuò)誤顯示錯(cuò)誤那么簡(jiǎn)單。有些時(shí)候我們要格式化輸入框的內(nèi)容。
例如,"1000"顯示成"1,000"
"hello"顯示成"Hello"
現(xiàn)在讓我們實(shí)現(xiàn)自動(dòng)首字母大寫(xiě)。
源碼如下:
<form name="form" novalidate="novalidate"> <input name="text" type="text" ng-model="name" upper-case> </form> .directive("upperCase",function(){ return { restrict:"A", require:"ngModel", link:function($scope,$element,$attrs,$ngModel){ $ngModel.$parsers.push(function(value){ var viewValue; if(angular.isUndefined(value)){ viewValue = ""; }else{ viewValue = "" + value; } viewValue = viewValue[0].toUpperCase() + viewValue.substring(1); //設(shè)置界面內(nèi)容 $ngModel.$setViewValue(viewValue); //渲染到界面上,這個(gè)函數(shù)很重要 $ngModel.$render(); return value; }) } } });
這里我們使用了$setViewValue和$render,$setViewValue設(shè)置viewValue為指定的值,$render把viewValue顯示到界面上。
很多人以為使用了$setViewValue就能更新界面了,沒(méi)有使用$render,最后不管怎么搞,界面都沒(méi)刷新。
如果只使用了$ngModel.$parsers是不夠的,$parsers只在用戶在輸入框輸入新內(nèi)容的時(shí)候觸發(fā),還有一種情況是需要重新刷新輸入框的內(nèi)容的:
那就是雙向綁定,例如剛才的輸入框綁定的是MainController中的$scope.name,當(dāng)用戶通過(guò)其他方式把$scope.name改成"hello",輸入框中看不到首字母大寫(xiě)。
這時(shí)候就要使用$formatters,還是先看個(gè)例子吧.
<body ng-controller="MainController"> <form name="form" novalidate="novalidate"> <button ng-click="random()">隨機(jī)</button> <input name="text" type="text" ng-model="name" upper-case> </form> </body>
MainController的內(nèi)容:
angular.module("app", []) .controller("MainController", function ($scope, $timeout) { $scope.random = function(){ $scope.name = "hello" + Math.random(); } })
夠簡(jiǎn)單吧,點(diǎn)擊按鈕的時(shí)候,$scope.name變成hello開(kāi)頭的隨機(jī)內(nèi)容.
很明顯,hello的首字母沒(méi)大寫(xiě),不是我們想要的內(nèi)容。
我們修改下指令的內(nèi)容:
.directive("upperCase",function(){ return { restrict:"A", require:"ngModel", link:function($scope,$element,$attrs,$ngModel){ $ngModel.$parsers.push(function(value){ var viewValue = upperCaseFirstWord(handleEmptyValue(value)); //設(shè)置界面內(nèi)容 $ngModel.$setViewValue(viewValue); //渲染到界面上,這個(gè)函數(shù)很重要 $ngModel.$render(); return value; }) //當(dāng)過(guò)外部設(shè)置modelValue的時(shí)候,會(huì)自動(dòng)調(diào)用$formatters里面函數(shù) $ngModel.$formatters.push(function(value){ return upperCaseFirstWord(handleEmptyValue(value)); }) //防止undefined,把所有的內(nèi)容轉(zhuǎn)換成字符串 function handleEmptyValue(value){ return angular.isUndefined(value) ? "" : "" + value; } //首字母大寫(xiě) function upperCaseFirstWord(value){ return value.length > 0 ? value[0].toUpperCase() + value.substring(1) : ""; } } } });
總結(jié)一下:
1.
-->用戶在輸入框輸入內(nèi)容
-->angular遍歷$ngModel.$parsers里面的函數(shù)轉(zhuǎn)換輸入的內(nèi)容,然后設(shè)置到$ngModel.$modelValue
-->在$ngModel.$parsers數(shù)組中的函數(shù)里,我們修改了$ngModel.$viewValue,然后$ngMode.$render()渲染內(nèi)容。
2.
-->通過(guò)按鈕生成隨機(jī)的字符串設(shè)置到name
-->每次臟檢測(cè)都會(huì)判斷name的值是否跟$ngModel.$modelValue不一致(這里是使用$watch實(shí)現(xiàn)的),不一致就反序遍歷$formaters里面的所有函數(shù)并執(zhí)行,把最終返回值賦值到$ngModel.$viewValue
-->刷新輸入框內(nèi)容
“自定義輸入框的顯示內(nèi)容”的例子能不能優(yōu)化?
為什么要優(yōu)化?
原因很簡(jiǎn)單,為了實(shí)現(xiàn)“自定義內(nèi)容”,使用了$parsers和$formatters,其實(shí)兩者的內(nèi)容很像!這一點(diǎn)很關(guān)鍵。
怎么優(yōu)化?
使用$ngModel.$validators。
好,現(xiàn)在把例子再改一下。
.directive("upperCase",function(){ return { restrict:"A", require:"ngModel", link:function($scope,$element,$attrs,$ngModel){ //1.3才支持,不管手動(dòng)輸入還是通過(guò)其他地方更新modelValue,都會(huì)執(zhí)行這里 $ngModel.$validators.uppercase = function(modelValue,viewValue){ var viewValue = upperCaseFirstWord(handleEmptyValue(modelValue)); //設(shè)置界面內(nèi)容 $ngModel.$setViewValue(viewValue); //渲染到界面上,這個(gè)函數(shù)很重要 $ngModel.$render(); //返回true,表示驗(yàn)證通過(guò),在這里是沒(méi)啥意義 return true; } //防止undefined,把所有的內(nèi)容轉(zhuǎn)換成字符串 function handleEmptyValue(value){ return angular.isUndefined(value) ? "" : "" + value; } //首字母大寫(xiě) function upperCaseFirstWord(value){ return value.length > 0 ? value[0].toUpperCase() + value.substring(1) : ""; } } } })
代碼簡(jiǎn)潔了很多,$ngModel.$validators在1.3以上的版本才支持。
$ngModel.$validators.uppercase函數(shù)的返回值如果是false,那么$ngModel.$error['uppercase']=true。這一點(diǎn)跟$ngModel.$setValidity("uppercase",false)差不多。
- AngularJS身份驗(yàn)證的方法
- AngularJS實(shí)現(xiàn)表單驗(yàn)證
- AngularJS使用ngMessages進(jìn)行表單驗(yàn)證
- AngularJS實(shí)現(xiàn)表單手動(dòng)驗(yàn)證和表單自動(dòng)驗(yàn)證
- 詳解AngularJS實(shí)現(xiàn)表單驗(yàn)證
- AngularJS使用angular-formly進(jìn)行表單驗(yàn)證
- AngularJS自動(dòng)表單驗(yàn)證
- AngularJs表單驗(yàn)證實(shí)例詳解
- 詳解Angular開(kāi)發(fā)中的登陸與身份驗(yàn)證
相關(guān)文章
Angular用來(lái)控制元素的展示與否的原生指令介紹
這篇文章主要介紹了Angular用來(lái)控制元素的展示與否的原生指令的用法及區(qū)別,非常詳細(xì),這里推薦給小伙伴們2015-01-01Angularjs之如何在跨域請(qǐng)求中傳輸Cookie的方法
跨域傳輸Cookie是需要后臺(tái)和前臺(tái)同時(shí)做相關(guān)處理才能解決的,這篇文章主要介紹了Angularjs之如何在跨域請(qǐng)求中傳輸Cookie的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06AngularJS 模塊詳解及簡(jiǎn)單實(shí)例
本文主要介紹AngularJS 模塊,這里幫大家整理了相關(guān)資料,詳細(xì)介紹了AngularJS的基礎(chǔ)知識(shí),有需要的朋友可以參考下2016-07-07如何利用@angular/cli V6.0直接開(kāi)發(fā)PWA應(yīng)用詳解
這篇文章主要給大家介紹了如何利用@angular/cli V6.0直接開(kāi)發(fā)PWA應(yīng)用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用@angular/cli V6.0具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2018-05-05詳解angularJS動(dòng)態(tài)生成的頁(yè)面中ng-click無(wú)效解決辦法
這篇文章主要介紹了詳解angularJS動(dòng)態(tài)生成的頁(yè)面中ng-click無(wú)效解決辦法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-06-06Angularjs實(shí)現(xiàn)頁(yè)面模板清除的方法
這篇文章主要介紹了Angularjs實(shí)現(xiàn)頁(yè)面模板清除的方法,需要的朋友可以參考下2018-07-07