2

I need to enable/disable all validation rules in Angular form or subform under ng-form="myForm" based on a scope variable $scope.isValidationRequired. So, if isValidationRequired is false, none of the validations set for the designated group of fields will run, and the result will always be myForm.$valid==true, otherwise, the validation rules will run as usual.

I did a lot of research, and realized that this feature is not available out of the box with Angular. However, I found some add-ons or with some customization, it is possible.

For example, I can use the add-on angular-conditional-validation (github and demo) with custom directive enable-validation="isValidationRequired". This will be perfect, except that I cannot apply this feature for a group of fields under ng-form. I have to add this directive for each and every field where applicable.

The other solution is to use custom validation using Angular $validators pipeline. This requires some extra effort and I don't have time since the sprint is almost over and I have to give some results in a few days.

If you have any other suggestions please post an answer.

Use Case:

To clarify the need for this, I will mention the use-case. The end user can fill the form with invalid data and he can click Save button and in this case, the validation rules shouldn't be triggered. Only when the user clicks Validate and Save then the validation rules should be fired.

Solution:

See the final plunker code here.

UPDATE: as per comments below, the solution will cause the browser to hang if inner subforms are used under ng-form. More effort is needed to debug and resolver this issuer. If only one level is used, then it works fine.

UPDATE: The plunker here was updated with a more general solution. Now the code will work with a form that has sub-forms under ng-form. The function setAllInputsDirty() checks if the object is a $$parentForm to stop recursion. Also, the changeValidity() will check if the object is a form using $addControl then it will call itself to validate its child objects. So far, this function works fine, but it needs a bit of additional optimization.

tarekahf
  • 738
  • 1
  • 16
  • 42

2 Answers2

1

One idea is to reset the errors in the digest loop if the validation flag is disabled. You can iterate through the form errors on change and set them to valid, one by one.

$scope.$watch(function() {
    $scope.changeValidity();
}, true);

$scope.changeValidity = function() {
    if ($scope.isValidationRequired === "false") {
        for (var error in $scope.form.$error) {
            while ($scope.form.$error[error]) {
                $scope.form.$error[error][0].$setValidity(error, true);
            }
        }
    }
}

Here is a plunkr: https://plnkr.co/edit/fH4vGVPa1MwljPFknYHZ

Larry Turtis
  • 1,907
  • 13
  • 26
  • I tried to access the plunker sample, and I am getting error "Unable to connect to any application instances.". Is this a global problem? – tarekahf Oct 21 '16 at 14:15
  • Sounds like plunkr was momentarily down. It's working for me, now. – Larry Turtis Oct 21 '16 at 14:34
  • Wow! Thanks a lot. One small issue. If validation is turned off, and there are validation error, then turned back on, the validation rules are not triggered. I tried to research the solution but didn't find a quick way to fix it. It seems I have to [set all form fields to dirty](http://blog.pixelastic.com/2014/09/10/setting-all-inputs-as-$dirty-in-angularjs-1.2/) but not working also. I also found [this](http://bvaughn.github.io/angular-form-for/#/demo/form-metadata) but will take a long time to digest. Please help. – tarekahf Oct 21 '16 at 16:13
  • Well, it looks like Plunkr is down again so I can't show this, but I think you should be able to iterate through the form and call $validate. I will post an example when Plunkr is back up. – Larry Turtis Oct 21 '16 at 16:32
  • Oh my God! It worked. Check it now, [this is your updated plunker code](https://plnkr.co/edit/ycPmYDSg6da10KdoNCiM?p=preview). Could you please kindly just make sure this code is good for production and if you have any feedback. Thank you so much. – tarekahf Oct 21 '16 at 16:39
  • when I implemented the code in my project, there is a dead loop in this code part `while ($scope.mainForm.$error[error]) { $scope.mainForm.$error[error][0].$setValidity(error, true); }`. When I debug, it won't clear the $valid flag using `$setValidity(error, true)`. Any idea? – tarekahf Oct 21 '16 at 19:01
  • What's the nature of the error? Can you replicate in a plunk? – Larry Turtis Oct 21 '16 at 20:03
  • There is no error. It is a infinite loop while setting validity to true for the error key. I will try to replicate, but it will be very difficult since my project is relatively huge. One thing I can think of is that the error happens in 2nd level `ng-form` subform, not on the main parent form. I'll try to create a subform on plunker and see what happens. – tarekahf Oct 21 '16 at 20:12
  • Shall we move this to chat? I got this recommendation from stackoverflow. However, I confirmed that if you add the **inner form**, turn on validation, all is good, then turn off, then the browser it will hang. See new plunker [here](https://plnkr.co/edit/ycPmYDSg6da10KdoNCiM?p=preview). – tarekahf Oct 21 '16 at 20:43
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/126364/discussion-between-larry-turtis-and-tarekahf). – Larry Turtis Oct 21 '16 at 20:48
0

This is the updated answer that will prevent infinite loop and infinite recursion. Also, the code depends on a known root form which can be tweaked a bit to make it more general.

References: Pixelastic blog and Larry's answer
Plunker: https://plnkr.co/edit/ycPmYDSg6da10KdoNCiM?p=preview
UPDATE: code improvements to make it work for multiple errors for each field in each subform, and loop to ensure the errors are cleared on the subform level

var app = angular.module('plunker', []);
app.controller('MainCtrl', ["$scope", function($scope) {
  $scope.isValidationRequired = true;
    var rootForm = "form";
    function setAllInputsDirty(scope) {
        angular.forEach(scope, function(value, key) {
            // We skip non-form and non-inputs
            if (!value || value.$dirty === undefined) {
                return;
            }
            // Recursively applying same method on all forms included in the form except the parent form
            if (value.$addControl && key !== "$$parentForm") {
                return setAllInputsDirty(value);
            }
            if (value.$validate){
                value.$validate();
            }
            // Setting inputs to $dirty, but re-applying its content in itself
            if (value.$setViewValue) {
                //debugger;
                return value.$setViewValue(value.$viewValue);
            }
        });
    }

  $scope.$watch(function() {
    $scope.changeValidity();
}, true);

    $scope.changeValidity = function(theForm) {
        debugger;
        //This will check if validation is truned off, it will 
        // clear all validation errors
        if (!theForm) {
          theForm = $scope[rootForm];
        }
        if ($scope.isValidationRequired === "false") {
            for (var error in theForm.$error) {
                errTypeArr = theForm.$error[error];
                angular.forEach (errTypeArr, function(value, idx) {
                    var theObjName = value.$name;
                    var theObj = value;
                    if (theObj.$addControl) {
                        //This is a subform, so call the function recursively for each of the children
                        var isValid=false;
                        while (!isValid) {
                            $scope.changeValidity(theObj);
                            isValid = theObj.$valid;
                        }
                    } else {
                      while (theObj.$error[error]) {
                          theObj.$setValidity(error, true);
                      }
                    }
                })
            }
        } else {
            setAllInputsDirty($scope);
        }
    }

}]);
Community
  • 1
  • 1
tarekahf
  • 738
  • 1
  • 16
  • 42