Share validation state for multiple fields against the same validator

Issue

I will start out stating that I have searched google and SO and I have not found an answer for this specific situation. Yes, there are other posts that sound the same but are more based on a “MoreThan / LessThan” mentality. This does not following that mentality at all so please do not mark this as a duplicate referring to them.

Check out the Plunker Example

I am attempting to make sure the user does not enter an address that already exists else where on the page. To do this I need to validate all the address fields since different locations may have the same street address. I need the validator to set all the related fields to valid if any are invalid once the address has been fixed to not be a duplicate. Currently it only sets the last field modified to valid and leaves the rest as invalid.

Plunker example demonstrates what is happening. I have tried many different approaches such as iterating through all fields and setting them to prestine and untouched and then setting them to dirty and touched to trigger validations again but I am having no luck getting this working.

Validator

   angular.directive('ruleFunc', ['$parse', function($parse) {
    return {
      restrict: 'A',
      require: 'ngModel',
      link: function($scope, $element, $attrs, $ngModel) {
        var validatorName = $attrs.ruleName;
        var validatorFunc = $attrs.ruleFunc;

        if (!angular.isDefined(validatorName)) {
          throw Error("rule-name attribute must be defined.");
        }

        if (!angular.isDefined(validatorFunc)) {
          throw Error("rule-func attribute must be defined.");
        }

        // in real code I passing a function call with the model as the param
        // this example demonstrated the issue I am having though
        var expressionHandler = $parse(validatorFunc);

        // had to use viewChangeListener because changes to the model 
        // were not showing up correctly in the actual implementation
        $ngModel.$viewChangeListeners.push(function() {
          var valid = expressionHandler($scope);
          $ngModel.$setValidity(validatorName, valid);
        });
      });

Form

<form name="AddressForm" novalidate>
<h1>Address Form</h1>
<div style="margin:20px">
  <input id="Street" type="text" name="Street" placeholder="Street" data-ng-model="ctrl.address.street" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.Street.$error.profileHasContact}}
  <br />

  <input id="City" type="text" name="City" placeholder="City" data-ng-model="ctrl.address.city" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.City.$error.profileHasContact}}
  <br />

  <input id="State" type="text" name="State" placeholder="State" data-ng-model="ctrl.address.state" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.State.$error.profileHasContact}}
  <br />

  <input id="Zip" type="text" name="Zip" placeholder="Zip" data-ng-model="ctrl.address.zip" rule-func="ctrl.checkVal()" rule-name="profileHasContact"> {{!AddressForm.Zip.$error.profileHasContact}}
  <br />

  <div ng-if="(AddressForm.Street.$error.profileHasContact 
         || AddressForm.City.$error.profileHasContact 
         || AddressForm.State.$error.profileHasContact
         || AddressForm.Zip.$error.profileHasContact)">Address already exists in Main Contacts</div>

  <button type="submit">Submit</button>
</div>

Solution

I did find a post that was close enough that I could hack together a solution.
Form validation – Required one of many in a group

Here is the updated plunker

Updated Validator

directive('ruleFunc', ['$parse', function($parse) {
    return {
      restrict: 'A',
      require: 'ngModel',
      link: function($scope, $element, $attrs, $ngModel) {
        var validatorName = $attrs.ruleName;
        var validatorFunc = $attrs.ruleFunc;
        var groupName = $attrs.ruleGroup;

        if (!angular.isDefined(validatorName)) {
          throw Error("rule-name attribute must be defined.");
        }

        if (!angular.isDefined(validatorFunc)) {
          throw Error("rule-func attribute must be defined.");
        }

        if(angular.isDefined(groupName)){

          // setup place to store groups if needed
          if (!$scope.__ruleValidationGroups) {
              $scope.__ruleValidationGroups = {};
          }
          var groups = $scope.__ruleValidationGroups;

          // setip group if needed
          if(!groups[groupName]){
            groups[groupName] = {};
          }
          var group = groups[groupName];

          // assign model to group
          group[$attrs.ngModel] = {
            model: $ngModel
          }
        }

        function updateValidity(valid){
          if(angular.isDefined(groupName)){

            // set all models in group to same validity
            for(var prop in group){
              if(group.hasOwnProperty(prop)){
                group[prop].model.$setValidity(validatorName, valid);
              }
            }

          }
          else
          {
            // set this model validity if not in group
            $ngModel.$setValidity(validatorName, valid);
          }
        }

        var expressionHandler = $parse(validatorFunc);
        $ngModel.$viewChangeListeners.push(function() {
          var valid = expressionHandler($scope);
          updateValidity(valid);
        });
      }
    };
  }]);

Answered By – Tony

Answer Checked By – Pedro (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.