4

I've got an AngularJS form that has a custom validator to check with the server backend whether the input value is unique or not. The unique validation is done by the mob-async-validate-unique directive in the example below.

The form looks somewhat like this:

<form class="form-floating" novalidate="novalidate" ng-submit="saveItem(item)">
    <div class="form-group filled">
        <label class="control-label">Key</label>
        <input type="text" class="form-control" ng-model="item.Key" mob-async-validate-unique >
    </div>

    <div class="form-group">
        <button type="submit" class="btn btn-lg btn-primary">Save</button>
    </div>

</form>

I want to use the same form for both adding as well as editing the model that I put on $scope.

Everything works great except for the fact that the unique validator fires on the edit operation even when the value is the same as the original value and then validates unique as false because the value already exists. The validator marks the field invalid in both the following cases:

  1. Change the field value and then edit it back to the original value
  2. Submit the form without changing anything

What is the best way to achieve this? The naive way I can think of is that I'll have to store the original value on a $scope.originalValue variable, and then add an attribute on the unique validated element to name this variable. Within the validator I'll read this value from the $scope and compare it with the current value to make the validator accept it when the two values are the same. I'm going ahead and implementing this.

I use the unique validator in a generic way in several places (yes, there are several more attributes in use on the <input> element that I've not included in the code sample, for simplicity and legibility) and need the validator to function completely on it's own and ideally want to keep the controller $scope out of the picture so that I can use the custom async validator anyway / anywhere I want.

Dhiraj Gupta
  • 9,704
  • 8
  • 49
  • 54

2 Answers2

2

Depending on your used version of angularjs, there is absolutely no need for writing a custom async validator. Angular has a built-in way of doing that. Check https://docs.angularjs.org/api/ng/type/ngModel.NgModelController

When you use the $asyncValidator as described in the docs, it does only validate against you API if all other validations succeed.

** EDIT ** Regarding your problem with async validation on editing an existing entry in your database, i suggest the following.

var originalData = {};
if(editMode) {
  originalData = data.from.your.API;
} 
$scope.formData = angular.copy(orignalData);

// in your async validator
if(value && value !== orginalData(key)) {
  //do async validation
} else if(value == originalData(key)) {
  return true;  //field is valid
}
Kyoss
  • 172
  • 1
  • 11
  • The link you provided gives an example, but not in a re-usable way. Making a validator directive is the only way (I know) to do that. Apart from that, code to resolve / reject will always be required. Yes, I'm aware that the async validators only fire after regular validators, like `required`, `number` etc. validate. Please read through the question, I need the validator to not reject in edit mode, when the value is the same. – Dhiraj Gupta Oct 29 '15 at 11:59
  • 1
    added an example for your case – Kyoss Nov 02 '15 at 10:39
1

here is my directive to solve problem

  • validate if user press key
  • if model value same as initialValue. Async validation will not be applied

    export default function ($http, API_URL, $q) {
      "ngInject";
      return {
        require: 'ngModel',
        restrict: 'A',
        scope: {
          asyncFieldValidator: '@',
          initialValue: '@'
        },
        link: ($scope, element, attrs, ngModel) => {
          const apiUrl = `${API_URL}${$scope.asyncFieldValidator}`;
    
          element.on('keyup', e => {
            ngModel.$asyncValidators.uniq = (modelValue, viewValue) => {
              const userInput = modelValue || viewValue;
              const checkInitial = $scope.initialValue === userInput;
              return !checkInitial
                ? $http.get(apiUrl + userInput)
                  .then(res => res.status === 204 ? true : $q.reject())
                : $q.resolve(`value is same`)
            }
          });
        }
      }
    }