• 从浅入深剖析angular表单验证


    最近手上维护的组件剩下的BUG都是表单验证,而且公司的表单验证那块代码经历的几代人,里面的逻辑开始变得不清晰,而且代码结构不是很angular。

    是很有必要深入了解表单验证。

    入门之前,我觉得应该先了解angular内置的表单验证有哪些:

    1,必填项

    验证某个表单是否已经填写,只要在元素上标记required即可:

    <input type="text"  required>

    2,最小长度

    验证表单输入框的内容是否大于某个最小值。

    <input ng-minlength="5">

    3,最大长度

    验证表单输入框的内容是否小于某个最大值、

    <input ng-maxlength="5">

    4,匹配正则

    确保输入的内容匹配某个正则。

    <input ng-pattern="[a-zA-Z]">

    5,电子邮件

    验证内容是否是电子邮件。

    <input type="email">

    6,数字

    验证输入内容是否是数字.

    <input type="number">

    7,URL

    验证输入内容是否是URL。

    <input type="url">

    了解内置的表单验证的很有必要的,可以避免重复开发。

    接着就可以看看最简单的表单验证例子。

    <body ng-controller="MainController">
        <form name="form" novalidate="novalidate">
            <input name="text" type="email" ng-model="name">
        </form>
    </body>

    ngModel是angular的黑魔法,实现双向绑定,当name的值变化的时候,input的value也会跟着变化。

    当用户在input修改value的时候,name的值也会跟着变化。

    novalidate="novalidate"的目的是去除系统自带的表单验证。

    上面那段代码解析完,angular会在MainController的$scope下面生成一个变量"form",$scope.form,这个变量的名称跟html中form.name一致。

    而$scope.form.text为文本输入框的Model,继承自ngModelController。

    其中$scope.form实例自FormController。其内容为:

    文本输入框的Model(也就是$scope.form.text)为:

    其中$dirty/$pristine,$valid/$invalid,$error为常用属性。尤其是$error。

     最简单的表单验证:

    了解了form和输入框,就可以先撸个最简单的显示错误的指令。

    html内容如下:

    <form name="form" novalidate="novalidate">
         <input name="text" type="email" ng-model="name" error-tip>
    </form>

    指令代码如下:

        //    当输入框出错,就显示错误
        directive("errorTip",function($compile){
                return {
                    restrict:"A",
                    require:"ngModel",
                    link:function($scope,$element,$attrs,$ngModel){
                        //创建子scope
                        var subScope = $scope.$new(),
                        //错误标签的字符串,有错误的时候,显示错误内容
                            tip = '<span ng-if="hasError()">{{errors() | json}}</span>';
                        //脏,而且无效,当然属于错误了
                        $scope.hasError = function(){
                            return $ngModel.$invalid && $ngModel.$dirty;
                        }
                        //放回ngModel的错误内容,其实就是一个对象{email:true,xxx:true,xxxx:trie}
                        $scope.errors = function(){
                            return $ngModel.$error;
                        }
                        //编译错误的指令,放到输入框后面
                        $element.after($compile(tip)(subScope));
                    }
                }
            });

    先看看执行结果:

    输入无效的邮箱地址的时候:

    输入正确的邮箱地址的时候:

    errorTip指令一开始通过  require:"ngModel"  获取ngModelController。然后创建用于显示错误的元素到输入框。

    这里使用了$compile,$compile用于动态编译显示html内容的。具体原理可以看这里:http://www.cnblogs.com/accordion/p/5156553.html.

    当有错误内容的时候,错误的元素就会显示。

    为什么subScope可以访问hasError和errors方法?

    因为原型链。看下图就知道了。

    自定义错误内容

    好了,很明显现在的表单验证是不能投入使用的,我们必须自定义显示的错误内容,而且要显示的错误不仅仅只有一个。

     显示多个错误使用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是一个过滤器,用于自定义显示错误信息的。过滤器其实是个函数。

    其代码如下:

            .filter("errorFilter",function(){
                return function(input){
                    var errorMessagesMap = {
                        email:"请输入正确的邮箱地址",
                        xxoo:"少儿不宜"
                    }
    
                    return errorMessagesMap[input];
                }
            });

    结果如下:

    好了,到这里就能够处理“简单”的表单验证了。对,简单的。我们还必须继续深入。

    自定义表单验证!

    那我们就来实现一个不能输入“帅哥”的表单验证吧。

    指令如下:

            .directive("doNotInputHandsomeBoy",function($compile){
                return {
                    restrict:"A",
                    require:"ngModel",
                    link:function($scope,$element,$attrs,$ngModel){
                        $ngModel.$parsers.push(function(value){
                            if(value === "帅哥"){
                                //设置handsome为无效,设置它为无效之后,$error就会变成{handsome:true}
                                $ngModel.$setValidity("handsome",false);
                            }
                            return value;
                        })
                    }
                }
            })

    结果如下:

    这里有两个关键的东西,$ngModel.$parsers和$ngModel.$setValidity.

    $ngModel.$parsers是一个数组,当在输入框输入内容的时候,都会遍历并执行$parsers里面的函数。

    $ngModel.$setValidity("handsome",false);设置handsome为无效,会设置$ngModel.$error["handsome"] = true;

    也会设置delete $ngModel.$$success["handsome"],具体可以翻翻源码。

    这里我总结一下流程。

    -->用户输入

    -->angular执行所有$parsers中的函数

    -->遇到$setValidity("xxoo",false);那么就会把xxoo当做一个key设置到$ngModel.$error["xxoo"]

    -->然后errorTip指令会ng-repeat   $ngModel.$error

    -->errorFilter会对错误信息转义

    -->最后显示错误的信息

     自定义输入框的显示内容

    很多时候开发,不是简简单单验证错误显示错误那么简单。有些时候我们要格式化输入框的内容。

    例如,"1000"显示成"1,000"

           "hello"显示成"Hello"

    现在让我们实现自动首字母大写。

    源码如下:

        <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);
                            //设置界面内容
                            $ngModel.$setViewValue(viewValue);
                            //渲染到界面上,这个函数很重要
                            $ngModel.$render();
                            return value;
                        })
                    }
                }
            });

    这里我们使用了$setViewValue和$render,$setViewValue设置viewValue为指定的值,$render把viewValue显示到界面上。

    很多人以为使用了$setViewValue就能更新界面了,没有使用$render,最后不管怎么搞,界面都没刷新。

     如果只使用了$ngModel.$parsers是不够的,$parsers只在用户在输入框输入新内容的时候触发,还有一种情况是需要重新刷新输入框的内容的:

    那就是双向绑定,例如刚才的输入框绑定的是MainController中的$scope.name,当用户通过其他方式把$scope.name改成"hello",输入框中看不到首字母大写。

    这时候就要使用$formatters,还是先看个例子吧.

    <body ng-controller="MainController">
        <form name="form" novalidate="novalidate">
            <button ng-click="random()">随机</button>
            <input name="text" type="text" ng-model="name" upper-case>
        </form>
    </body>

    MainController的内容:

        angular.module("app", [])
            .controller("MainController", function ($scope, $timeout) {
                $scope.random = function(){
                    $scope.name = "hello" + Math.random();
                }
            })

    够简单吧,点击按钮的时候,$scope.name变成hello开头的随机内容.

    很明显,hello的首字母没大写,不是我们想要的内容。

    我们修改下指令的内容:

            .directive("upperCase",function(){
                return {
                    restrict:"A",
                    require:"ngModel",
                    link:function($scope,$element,$attrs,$ngModel){
                        $ngModel.$parsers.push(function(value){
                            var viewValue = upperCaseFirstWord(handleEmptyValue(value));
                            //设置界面内容
                            $ngModel.$setViewValue(viewValue);
                            //渲染到界面上,这个函数很重要
                            $ngModel.$render();
                            return value;
                        })
                        //当过外部设置modelValue的时候,会自动调用$formatters里面函数
                        $ngModel.$formatters.push(function(value){
                            return upperCaseFirstWord(handleEmptyValue(value));
                        })
    
                        //防止undefined,把所有的内容转换成字符串
                        function handleEmptyValue(value){
                            return angular.isUndefined(value) ? "" : "" + value;
                        }
                        
                        //首字母大写
                        function upperCaseFirstWord(value){
                            return value.length > 0 ? value[0].toUpperCase() + value.substring(1) : "";
                        }
                    }
                }
            });

    总结一下:

    1.

    -->用户在输入框输入内容

    -->angular遍历$ngModel.$parsers里面的函数转换输入的内容,然后设置到$ngModel.$modelValue

    -->在$ngModel.$parsers数组中的函数里,我们修改了$ngModel.$viewValue,然后$ngMode.$render()渲染内容。

     

    2.

    -->通过按钮生成随机的字符串设置到name

    -->每次脏检测都会判断name的值是否跟$ngModel.$modelValue不一致(这里是使用$watch实现的),不一致就反序遍历$formaters里面的所有函数并执行,把最终返回值赋值到$ngModel.$viewValue

    -->刷新输入框内容

    “自定义输入框的显示内容”的例子能不能优化?

    为什么要优化?

    原因很简单,为了实现“自定义内容”,使用了$parsers和$formatters,其实两者的内容很像!这一点很关键。

    怎么优化?

    使用$ngModel.$validators。

    好,现在把例子再改一下。

           .directive("upperCase",function(){
                return {
                    restrict:"A",
                    require:"ngModel",
                    link:function($scope,$element,$attrs,$ngModel){
                        //1.3才支持,不管手动输入还是通过其他地方更新modelValue,都会执行这里
                        $ngModel.$validators.uppercase = function(modelValue,viewValue){
                            var viewValue = upperCaseFirstWord(handleEmptyValue(modelValue));
                            //设置界面内容
                            $ngModel.$setViewValue(viewValue);
                            //渲染到界面上,这个函数很重要
                            $ngModel.$render();
                            //返回true,表示验证通过,在这里是没啥意义
                            return true;
                        }
    
                        //防止undefined,把所有的内容转换成字符串
                        function handleEmptyValue(value){
                            return angular.isUndefined(value) ? "" : "" + value;
                        }
    
                        //首字母大写
                        function upperCaseFirstWord(value){
                            return value.length > 0 ? value[0].toUpperCase() + value.substring(1) : "";
                        }
                    }
                }
            })

    代码简洁了很多,$ngModel.$validators在1.3以上的版本才支持。

    $ngModel.$validators.uppercase函数的返回值如果是false,那么$ngModel.$error['uppercase']=true。这一点跟$ngModel.$setValidity("uppercase",false)差不多。

    内容不完整的话,请拍砖,我继续加。

    晚点补上源码剖析部分。

  • 相关阅读:
    Vue--运行项目发送http://localhost:8080/sockjs-node/info请求报错,造成浏览器不能热更新
    Vue笔记--同局域网下访问本地项目
    Vue笔记--通过自定义指令实现按钮操作权限
    css/css3实现未知宽高元素的垂直居中和水平居中
    【转载】Vue路由history模式踩坑记录:nginx配置解决404问题
    给动态生成的input框,添加readonly属性
    layui-form下隐藏元素的验证问题
    layui的省市县三级联动
    webstorm-激活码
    采坑
  • 原文地址:https://www.cnblogs.com/geilishu/p/5635172.html
Copyright © 2020-2023  润新知