Recently, the remaining bugs of the components maintained in hand are form verification, and the logic inside the company's form verification code has been unclear for generations, and the code structure is not very angular.
It is very necessary to have an in-depth understanding of form verification.
<body ng-controller="MainController"><form name="form" novalidate="novalidate"><input name="text" type="email" ng-model="name"></form></body>
ngModel is an angular black magic, which realizes two-way binding. When the value of name changes, the input value will also change.
When the user input changes the value, the value of the name will also change.
The purpose of novalidate="novalidate" is to remove the form verification that comes with the system.
After parsing the above code, angular will generate a variable "form" and $scope.form under the $scope of MainController. The name of this variable is the same as form.name in html.
And $scope.form.text is the Model of the text input box, inherited from ngModelController.
Where $scope.form instance is from FormController. Its content is:
The Model (that is, $scope.form.text) of the text input box is:
where $dirty/$pristine,$valid/$invalid,$error are common attributes. Especially $error.
The easiest form verification:
After understanding the form and input box, you can first edit the simplest command to display the error.
The html content is as follows:
<form name="form" novalidate="novalidate"><input name="text" type="email" ng-model="name" error-tip></form>
The command code is as follows:
// When the input box error occurs, the error is displayed directive("errorTip",function($compile){return {restrict:"A",require:"ngModel",link:function($scope,$element,$attrs,$ngModel){//Create subscopevar subScope = $scope.$new(),//When there is an error, the error content is displayed tip = '<span ng-if="hasError()">{{errors() | json}}</span>';//Dirty and invalid, of course it is an error. $scope.hasError = function(){return $ngModel.$invalid && $ngModel.$dirty;} //Put back the error content of ngModel, it is actually an object {email:true,xxx:true,xxxx:trie}$scope.errors = function(){return $ngModel.$error;}//Compile the wrong instruction and put it in the input box after $element.after($compile(tip)(subScope));}}});Let's take a look at the execution results first:
When entering an invalid email address:
When entering the correct email address:
The errorTip directive starts by obtaining the ngModelController through require:"ngModel" . Then create an element to display the error into the input box.
$compile is used here, and $compile is used to dynamically compile and display html content.
When there is an error content, the wrong element will be displayed.
Why can subScope access hasError and errors methods?
Because of the prototype chain. Just look at the picture below to know.
Customize error content
OK, it is obvious that form verification is not available now. We must customize the error content to be displayed, and there is more than one error to be displayed.
Use ng-repeat to display multiple errors, that is, use the "errorTip" command
tip = '<span ng-if="hasError()">{{errors() | json}}</span>';Change to:
tip = '<ul ng-if="hasError()" ng-repeat="(errorKey,errorValue) in errors()">' +'<span ng-if="errorValue">{{errorKey | errorFilter}}</span>' +'</ul>';where errorFilter is a filter for custom display of error messages. A filter is actually a function.
The code is as follows:
.filter("errorFilter",function(){return function(input){var errorMessagesMap = {email:"Please enter the correct email address", xxoo:"Not suitable for children"}return errorMessagesMap[input];}});The results are as follows:
OK, here we can handle "simple" form verification. Yes, simple. We must continue to go deeper.
Custom form verification!
Then let’s implement a form verification that cannot be entered in “handsome guy”.
The instructions are as follows:
.directive("doNotInputHandsomeBoy",function($compile){return {restrict:"A",require:"ngModel",link:function($scope,$element,$attrs,$ngModel){$ngModel.$parsers.push(function(value){if(value === "Handsome"){//Set handsome to invalid, after setting it to invalid, $error will become {handsome:true}$ngModel.$setValidity("handsome",false);}return value;})}}})The results are as follows:
There are two key things here, $ngModel.$parsers and $ngModel.$setValidity.
$ngModel.$parsers is an array. When entering content in the input box, it will traverse and execute the functions in $parsers.
$ngModel.$setValidity("handsome",false); Setting handsome to invalid will set $ngModel.$error["handsome"] = true;
Delete $ngModel.$$success["handsome"] is also set, and you can search for the source code for details.
Here I will summarize the process.
-->User input
-->angular executes all functions in $parsers
-->Inspect $setValidity("xxoo",false); then xxoo will be set to $ngModel.$error["xxoo"]
-->Then the errorTip instruction will ng-repeat $ngModel.$error
-->errorFilter will escape the error message
-->The error message was displayed at the end
Customize the display content of the input box
Many times, development is not as simple as verifying errors and displaying errors. Sometimes we have to format the contents of the input box.
For example, "1000" is displayed as "1,000"
"hello" appears as "Hello"
Now let's implement automatic initials.
The source code is as follows:
<form name="form" novelidate="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);//Set the interface content $ngModel.$setViewValue(viewValue);//Resource to the interface, this function is very important $ngModel.$render(); return value;})}}});Here we use $setViewValue and $render, $setViewValue to set viewValue to the specified value, and $render displays viewValue to the interface.
Many people think that using $setViewValue can update the interface, but they do not use $render, and in the end, the interface has not been refreshed no matter what it does.
If only $ngModel.$parsers is not enough, $parsers is only triggered when the user enters new content in the input box. Another situation is that the content of the input box needs to be refreshed:
That is two-way binding. For example, the input box just now is bound to $scope.name in MainController. When the user changes $scope.name to "hello" in other ways, the capitalization of the first letter cannot be seen in the input box.
At this time, you need to use $formatters, so let’s see an example first.
<body ng-controller="MainController"><form name="form" novalidate="novalidate"><button ng-click="random()">Random</button><input name="text" type="text" ng-model="name" upper-case></form></body>
MainController content:
angular.module("app", []).controller("MainController", function ($scope, $timeout) {$scope.random = function(){$scope.name = "hello" + Math.random();}})It's simple enough. When you click the button, $scope.name becomes a random content starting with hello.
It is obvious that the initial letter of hello is not capitalized, not what we want.
We modify the content of the following command:
.directive("upperCase",function(){return {restrict:"A",require:"ngModel",link:function($scope,$element,$attrs,$ngModel){$ngModel.$parsers.push(function(value){var viewValue = upperCaseFirstWord(handleEmptyValue(value));//Set the interface content $ngModel.$setViewValue(viewValue);//Render to the interface, this function is very important$ngModel.$render(); return value;})//When the external modelValue is set, the function $ngModel.$formatters.push(function(value){return upperCaseFirstWord(handleEmptyValue(value));})//Prevent undefined and convert all content into string function handleEmptyValue(value){return angular.isUndefined(value) ? "" : "" + value;}//Preceive capital function upperCaseFirstWord(value){return value.length > 0 ? value[0].toUpperCase() + value.substring(1): "";}}}});To sum up:
1.
-->User enters content in the input box
-->angular traversal of the function in $ngModel.$parsers converts the input content, and then sets it to $ngModel.$modelValue
-->In the function in the $ngModel.$parsers array, we modify $ngModel.$viewValue, and then $ngMode.$render() renders the content.
2.
--> Generate random strings through buttons to set to name
-->Every time the dirty detection will determine whether the value of the name is inconsistent with $ngModel.$modelValue (here is implemented using $watch). If the inconsistent, iterate through all functions in $formaters in reverse order and execute, and assign the final return value to $ngModel.$viewValue
-->Refresh the input box content
Can the example "Customize the display content of the input box" be optimized?
Why optimize?
The reason is very simple. In order to implement "custom content", $parsers and $formatters are used. In fact, the content of the two is very similar! This is very important.
How to optimize?
Use $ngModel.$validators.
OK, now change the example.
.directive("upperCase",function(){return {restrict:"A",require:"ngModel",link:function($scope,$element,$attrs,$ngModel){//1.3 is supported. Whether you enter manually or update modelValue through other places, $ngModel.$validators.uppercase = function(modelValue,viewValue){var viewValue = upperCaseFirstWord(handleEmptyValue(modelValue));//Set the interface content $ngModel.$setViewValue(viewValue);//Resource to the interface, this function is very important $ngModel.$render();//Return true, indicating that verification is passed, there is no meaning here return true;}//Prevent undefined, converting all content into string function handleEmptyValue(value){return angular.isUndefined(value) ? "" : "" + value;}//Presenter the initial letter function upperCaseFirstWord(value){return value.length > 0 ? value[0].toUpperCase() + value.substring(1) : "";}}}})The code is much simpler, only supported by $ngModel.$validators in version 1.3 or above.
If the return value of $ngModel.$validators.uppercase function is false, then $ngModel.$error['uppercase']=true. This is similar to $ngModel.$setValidity("uppercase",false).