0

I'm trying to unite the AngularJS validation model with the Bootstrap form validation display.

  • If a user loads an empty form, I don't want the form to display error message right away. I want to wait until the user interacts with the form.
  • If a user submit the form with required fields not filled out, I also want to display an error message.
  • If a user starts typing in the field, I want error messages to show up right away.

So I have to check myForm.$submitted, myForm.<fieldName>.$dirty as well as myForm.<fieldName>.$touched.

However, it makes a lot of duplicated code with very few variation.

I've tried to make a directive to fix this issue but I can't seem to find the right way to wrap this complexity away.

HTML:

<div class="form-group required" ng-class="{ 'has-error': myForm.firstname.$invalid && (myForm.firstname.$dirty || myForm.$submitted || myForm.firstname.$touched)  }">
    <label for="firstname" class="control-label" translate>Profile.FirstName</label>
    <input type="text" class="form-control" id="firstname" name="firstname" required ng-model="vm.profile.firstName"/>
    <p class="help-block" ng-if="myForm.firstname.$error.required" translate>Forms.Default.Required</p>
</div>

I want to take the whole ng-class attribute and replace it by something more succinct. The directive seemed like the way to go so tried this:

(function(){
    'use strict';
    angular.module('app')
    .directive('hasError', [function(){
        return {
            restrict: 'A',
            scope: {
                form: '=bsForm',
                control: '=bsControl'
            },
            link: function(scope, element){
                scope.$watch('form', function(){
                    var isInvalid = scope.control.$invalid && scope.control.$dirty;
                    element.toggleClass('has-error', isInvalid);
                });

            }
        };
    }]);
})();

Usage:

<div class="form-group required" has-error bs-form="myForm" bs-control="myForm.firstname">
...
</div>

This however was not refreshing when properties of form changed.

What am I missing?

2 Answers 2

1

So... I managed to make a directive work properly for exactly my usage.

If there is a better way, please prove me wrong.

(function(){
    'use strict';
    angular.module('app')
    .directive('hasError', [function(){
        return {
            restrict: 'A',
            scope: {
                form: '=bsForm',
                control: '=bsControl'
            },
            link: function(scope, element){
                scope.$watchGroup(['control.$invalid', 'control.$dirty', 'control.$touched', 'form.$submitted'], function(){
                    var isInvalid = scope.control.$invalid && (scope.control.$dirty || scope.form.$submitted || scope.control.$touched);
                    element.toggleClass('has-error', isInvalid);
                });

            }
        };
    }]);
})();
Sign up to request clarification or add additional context in comments.

Comments

0

I did something like this one. My solution took a slightly different approach, but it may be helpful here (you can view the gist on Github).

Essentially, what I do is wrap all my form data inside a single object and I assign that object to a <form> attribute. I then watch that object and any time it changes, I select all elements with the ng-dirty and ng-invalid classes (this selector could be changed to whatever you like). I then loop through each of these elements and update messages for each of them.

Here's the code:

(function() {
  "use strict"
  angular.module('app')
    .directive('formValidator', function() {
      return {
        require: '^form',
        scope: {
          formData: '=',
          validateAll: '='
        },
        link: function(scope, element, attrs, ctrls) {
          window.frm = ctrls;
          var selector = '.ng-dirty.ng-invalid';

          function validate() {
            $(".formValidator-input-validation-error-message").remove();
            element.find(selector).each(function(index, el) {
              $el = $(el);
              var messages = [];
              var classes = $el.attr('class').match(/[\d\w-_]+/g);
              for (var i in classes) {
                var lastIndex = classes[i].lastIndexOf('-invalid-');
                if (lastIndex != -1) {
                  var validationMessageAttr = "data-" + classes[i].substr(lastIndex + 9) + "-validation-message";
                  var msg = $el.attr(validationMessageAttr);
                  if (!msg) {
                    msg = element.attr(validationMessageAttr);
                    if (!msg) {
                      msg = "Invalid!";
                    }
                  }
                  messages.push("<div class='validator'>" + msg + "</div>");
                }
              }
              $(el).after("<div style='position:absolute;' class='formValidator-input-validation-error-message'>" + messages.join() + "</div>");
            });
          }
          scope.$watch(function() {
            return scope.formData;
          }, function() {
            validate();
          }, true);
          scope.$watch('validateAll', function(newValue, oldValue) {
            selector = !!newValue ? '.ng-invalid' : '.ng-dirty.ng-invalid';
            validate();
          });
        }
      };
    })
})();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.