Can I programmatically apply Angular validation di

2020-02-24 07:57发布

I have found great many occurrences of the following pattern for html inputs, this being for phone numbers:

<input type="text" ng-model="CellPhoneNumber" required ng-pattern="/^[0-9]+$/" ng-minlength="10" />

I would like to create a custom directive that, wherever applied, will tell Angular to apply all three of these rules, e.g:

<input type="text" ng-model="CellPhoneNumber" bk-ng-validation="phoneNumber"/>

Then, code in my directive would find and invoke a function called phoneNumber, in which I would like to see something like:

Listing 1:

function bkNgPhoneNumber(model) {
    // This is purely SPECULATIVE pseudo-code, just to convey an idea.
    model.errors.add(applyMinLength(10, model));
    model.errors.add(applyMaxLength(15, model));
    model.errors.add(applyPattern("/^[0-9]+$/", model));
}

I would prefer the above approach over 'rewriting code for these rules, e.g:

Listing 2:

function phoneNumber(model) {
    if (model.length < 10 || model.length > 15) {
        model.errors.add("Must be 10 to 15 chars!");
    }
}

I don't want to do away with all attribute based directives, but preferably create a 'macro' directive that will invoke my Listing 1 code, which will intern invoke a set of more 'micro' validations.

5条回答
SAY GOODBYE
2楼-- · 2020-02-24 08:01

You can try this approach:

.directive('bkNgValidation', function () {
  return: {
    link: function (scope, element, attrs) {
      if (attrs['bk-ng-validation'] === 'phoneNumber') {
       element.$validateModel(function (value, validator) {
         if (value.length < 10 || value.length > 15) {
           validator.$setValidity('phone', true);
         } else {
           validator.$setValidity('phone', false);
         }
       });
      }
    }
  }
})
查看更多
The star\"
3楼-- · 2020-02-24 08:10

If you're using more validations, you can create a service that is responsible for identifying and validating the elements, without any limitation. Default directives of angular remain.

Example:

    module.service('$Validation', ["$compile",function($compile){

        this.validators = {
            'phoneNumber': [['required', 1], ['minlength',6], ['maxlength', 10], ['pattern', /^[0-9]+$/.source]],
            'phoneNumber2Custom': function(value){ 
                return /^[0-9]{6,10}$/.test(value) 
            },
            'userTwitter': function(value){
                return /^@(.+)/.test(value)
            }
            // ...etc... /
        }

        this.add = function(scope, element, attrs, model){
            var name = attrs.bkNgValidation, type;
            if(!(type = this.validators[name])) return;
            else if(angular.isFunction(type)) return (model.$validators[name] = type);

            element.removeAttr("bk-ng-validation");
            angular.forEach(type, function(expr){
                element.attr(expr[0], expr[1])
            });
            $compile(element)(scope)        
        };

    }]).directive('bkNgValidation', ["$Validation", function ($Validation) {
        return {
            require: '?ngModel',
            priority: 1e5,
            link: function(){
                $Validation.add.apply($Validation, arguments);
            }
        }
    }])

Demo

查看更多
▲ chillily
4楼-- · 2020-02-24 08:13

You are going in the opposite way, because you are assuming that directives are that much laborious to maintain and want to keep one to give all the validation you need, depending on the element.

That's an interesting approach but you need to be warned about the modularity of this approach: give this much of labour to one directive only is counterintuitive over the best practices of doing a "pure Angular way" to do the things.

If you want to proceed with this idea, I suggest you to look over the ngModelController (AngularJS Docs) properties, that can be injected on link() function of one directive. More precisely, the $validators.

You can add how many $validators to a NgModel controller you want.

During your validation, you can set/unset validity to the element returning a boolean:

app.directive('validator', function () {
    var definition = {
        restrict: 'A',
        require: '?ngModel',
        link: function (scope, element, attrs, ngModel) {
            // Return if no ngModelController
            if (!ngModel) {
                return;
            }

            ngModel.$validators.validateLength = function (modelValue, viewValue) {
                // modelValue === the value of the ngModel, on the script side
                // viewValue === the value of the ngModel, on the HTML (rendered) side
                // 
                // you can set-up $parsers, $formatters, $validators, etc, to handle the element

                return !(modelValue.length > 20);
            }
        }
    };

    return definition;
});

I suggest you to read more about this implementation because certain operations can interrupt the flux of $digest cycles on angular over the manipulated element.

Edit 1:

Like I've mentioned on the comments, here's a Plunkr with an working example.

查看更多
三岁会撩人
5楼-- · 2020-02-24 08:24

One way to do this (i.e. apply existing validators without writing their code again) would be to add the validation directives' respective attributes and force a re-compile. This would require your directive to have a high-enough priority and also be terminal: true.

app.directive("bkNgValidation", function($compile){
  return {
    priority: 10000,
    terminal: true,
    link: function(scope, element){
      element.attr("ng-required", "true");
      element.attr("ng-minlength", 20);
      element.attr("ng-maxlength", 30);

      // prevent infinite loop
      element.removeAttr("bk-ng-validation");

      $compile(element)(scope);
    }
  };
});

Demo

查看更多
The star\"
6楼-- · 2020-02-24 08:25

You can create a new component which including control with all required validators. Your component would look like:

<my-control name="field" ng-model="text"></my-control>

All required logic component should keep inside. To do it, create the my-control directive with template. Inside the template you can place an input with validation attributes:

<input type="text" ng-model="value" class="form-control" ng-pattern="'^(?!ng-*)'" minlength="3">

Then you need to bind ng-model value on your component to input:

angular.module('app', []).directive('myControl', function() {
   return {
       restrict: 'E',
       require: 'ngModel', //import ngModel into linking function
       templateUrl: 'myControl.tpl',
       scope: {}, //our component can have private properties, isolate it
       link: function(scope, elm, attrs, ngModel) {
           // reflect model changes via js
           ngModel.$render = function() {
               scope.value = ngModel.$viewValue;
           };
           // update model value after changes in input
           scope.$watch('value', function(value) {
               ngModel.$setViewValue(value);
           });
       }
   };
});

Here is a demo when you can see this component in action and how it works.

查看更多
登录 后发表回答