Skip to content Skip to sidebar Skip to footer

Angularjs Custom Form Validation Using $http

I have a form that looks like this:

Solution 1:

You don't need to make $http request in directive, better place for it is controller.

You can specify method inside controller - $scope.saveDeployment = function () { // here you make and handle your error on request ... }; you'll save error to scope and then create a directive that will watch $scope.yourResponseObject and set validity based on it.

Also if you need something like request and error on input field blur instead, you need to create a simple directive with elem.bind('blur', ...) where you call $scope.saveDeployment with callback to handle validity.

Take a look on the examples, there might be something similar - https://github.com/angular/angular.js/wiki/JsFiddle-Examples

Solution 2:

Validating a form input field using an asynchronous $http ajax call is a common need, but I haven't found any implementations that were complete, reusable, and easy to use so I tried my best to make one.

This functionality is especially useful for checking if a username, email, or other field/column is unique, but there are plenty of other use cases where a value must be validated with an ajax call (as in your example).

My solution has the following features:

  • Accepts a "check" function from the $scope that makes the $http call or any kind of validation (synchronous or asynchronous)
  • Accepts a "gate" function from the $scope that allows the check to be bypassed based on the value or ngModel state.
  • Debounces the "check" function's execution until the user has stopped typing
  • Ensure that only the latest $http call result is used (in case multiple are fired and return out of order).
  • Allows for state bindings so that the UI can respond appropriately and conveniently.
  • Customizable debounce time, check/gate functions, binding names and validation name.

My directive is pmkr-validate-custom (GitHub). It can be used for any asynchronous validation. I've tested it in several versions as far back as 1.1.5.

Here is a sample usage with Twitter Bootstrap in which I check if a username is unique.

Live Demo

<form name="the_form"class="form-group has-feedback">
  <divng-class="{'has-success':userNameUnique.valid, 'has-warning':userNameUnique.invalid}"><labelfor="user_name">Username</label><inputname="user_name"ng-model="user.userName"pmkr-validate-custom="{name:'unique', fn:checkUserNameUnique, gate:gateUserNameUnique, wait:500, props:'userNameUnique'}"pmkr-pristine-original=""class="form-control"
    ><spanng-show="userNameUnique.valid"class="glyphicon glyphicon-ok form-control-feedback"></span><spanng-show="userNameUnique.invalid"class="glyphicon glyphicon-warning-sign form-control-feedback"></span><ing-show="userNameUnique.pending"class="glyphicon glyphicon-refresh fa-spin form-control-feedback"></i><png-show="userNameUnique.valid"class="alert alert-success">"{{userNameUnique.checkedValue}}" is availiable.</p><png-show="userNameUnique.invalid"class="alert alert-warning">"{{userNameUnique.checkedValue}}" is not availiable.</p><buttonng-disabled="the_form.$invalid || the_form.user_name.$pristine || userNameUnique.pending"class="btn btn-default"
    >Submit</button></div></form>

Sample controller:

// Note that this ought to be in a service and referenced to $scope. This is just for demonstration.$scope.checkUserNameUnique = function(value) {
  return$http.get(validationUrl+value).then(function(resp) {
    // use resp to determine if value validreturn isValid; // true or false
  });
}

// The directive is gated off when this function returns true.$scope.gateUserNameUnique = function(value, $ngModel) {
  return !value || $ngModel.$pristine;
};

If I make any improvements, they will be up-to-date on GitHub, but I am also going to put the code here for this directive and its dependencies (may not be updated). I welcome suggestions or issues though GitHub issues!

angular.module('pmkr.validateCustom', [
  'pmkr.debounce'
])

.directive('pmkrValidateCustom', [
  '$q',
  'pmkr.debounce',
  function($q, debounce) {

    var directive = {
      restrict: 'A',
      require: 'ngModel',
      // set priority so that other directives can change ngModel state ($pristine, etc) before gate function
      priority: 1,
      link: function($scope, $element, $attrs, $ngModel) {

        var opts = $scope.$eval($attrs.pmkrValidateCustom);

        // this reference is used as a convenience for $scope[opts.props]var props = {
          pending : false,
          validating : false,
          checkedValue : null,
          valid : null,
          invalid : null
        };
        // if opts.props is set, assign props to $scope
        opts.props && ($scope[opts.props] = props);

        // debounce validation functionvar debouncedFn = debounce(validate, opts.wait);
        var latestFn = debounce.latest(debouncedFn);

        // initially valid$ngModel.$setValidity(opts.name, true);

        // track gated statevar gate;

        $scope.$watch(function() {
          return$ngModel.$viewValue;
        }, valueChange);

        // set model validity and props based on gated statefunctionsetValidity(isValid) {
          $ngModel.$setValidity(opts.name, isValid);
          if (gate) {
            props.valid = props.invalid = null;
          } else {
            props.valid = !(props.invalid = !isValid);
          }
        }

        functionvalidate(val) {
          if (gate) { return; }
          props.validating = true;
          return opts.fn(val);
        }

        functionvalueChange(val) {

          if (opts.gate && (gate = opts.gate(val, $ngModel))) {
            props.pending = props.validating = false;
            setValidity(true);
            return;
          }

          props.pending = true;
          props.valid = props.invalid = null;

          latestFn(val).then(function(isValid) {
            if (gate) { return; }
            props.checkedValue = val;
            setValidity(isValid);
            props.pending = props.validating = false;
          });

        }

      } // link

    }; // directivereturn directive;

  }
])

;

angular.module('pmkr.debounce', [])

.factory('pmkr.debounce', [
  '$timeout',
  '$q',
  function($timeout, $q) {

    var service = function() {
      return debounceFactory.apply(this, arguments);
    };
    service.immediate = function() {
      return debounceImmediateFactory.apply(this, arguments);
    };
    service.latest = function() {
      return debounceLatestFactory.apply(this, arguments);
    };

    functiondebounceFactory(fn, wait) {

      var timeoutPromise;

      functiondebounced() {

        var deferred = $q.defer();

        var context = this;
        var args = arguments;

        $timeout.cancel(timeoutPromise);

        timeoutPromise = $timeout(function() {
          deferred.resolve(fn.apply(context, args));
        }, wait);

        return deferred.promise;

      }

      return debounced;

    }

    functiondebounceImmediateFactory(fn, wait) {

      var timeoutPromise;

      functiondebounced() {

        var deferred = $q.defer();

        var context = this;
        var args = arguments;

        if (!timeoutPromise) {
          deferred.resolve(fn.apply(context, args));
          // return here?
        }

        $timeout.cancel(timeoutPromise);
        timeoutPromise = $timeout(function() {
          timeoutPromise = null;
        }, wait);

        return deferred.promise;

      }

      return debounced;

    }

    functiondebounceLatestFactory(fn) {

      var latestArgs;

      functiondebounced() {

        var args = latestArgs = JSON.stringify(arguments);

        var deferred = $q.defer();

        fn.apply(this, arguments).then(function(res) {
          if (latestArgs === args) {
            deferred.resolve(res);
          }
        }, function(res) {
          if (latestArgs === args) {
            deferred.reject(res);
          }
        });

        return deferred.promise;

      }

      return debounced;

    }

    return service;

  }
])

;

angular.module('pmkr.pristineOriginal', [])

.directive('pmkrPristineOriginal', [
  function() {

    var directive = {
      restrict : 'A',
      require : 'ngModel',
      link: function($scope, $element, $atts, $ngModel) {

        var pristineVal = null;

        $scope.$watch(function() {
          return$ngModel.$viewValue;
        }, function(val) {
          // set pristineVal to newVal the first time this function runsif (pristineVal === null) {
            pristineVal = $ngModel.$isEmpty(val) ? '' : val.toString();
          }

          // newVal is the original value - set input to pristine stateif (pristineVal === val) {
            $ngModel.$setPristine();
          }

        });

      }
    };

    return directive;

  }
])

;

Solution 3:

My solution was taken from Kosmetika's idea.

I used the angular-ui project and set an onBlur callback that was on the controller which called the web service via $http.

This set a controller/model property to true or false.

I then had a <span> use ng-show to watch the controller/model property so when the web service returned it would show the user information

Post a Comment for "Angularjs Custom Form Validation Using $http"