Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using AngularJS, how do I set all form fields to $dirty at once?

Tags:

angularjs

I have created a HTML form using AngularJS and added required attributes to some fields.

For these fields, I have an error message that displays if the field is not $pristine and also $invalid:

<input type="text" ng-model="SomeModel.SomeProperty" name="myField" class="input-block-level" required>
<p ng-show="frmMyForm.myField.$invalid && !frmMyForm.myField.$pristine" class="error-text">This field is required!</p>

This works fine. However, if the user simply skips over a required field (never places the cursor in it), then the field is always pristine and therefore the error message isn't displayed, even after clicking the submit button. So the user is faced with a form they can't submit but no error text to tell them why.

My thought is that setting all form fields to $dirty on the submit action would trigger the error messages to show up for any required field that the user simply skipped. Is this possible? If so, how?

Thanks in advance.

like image 850
Matt Cashatt Avatar asked Jan 30 '14 20:01

Matt Cashatt


People also ask

How do I refresh a form in AngularJS?

1 Answer. Show activity on this post. $('#form_id'). trigger("reset");

What is $dirty in AngularJS?

$dirty means the user has changed the input value, $invalid means the address itself is invalid. Therefore the error is only shown if the user has actively changed the input value to either an empty or invalid value.

What is setPristine ()?

$setPristine();Sets the form to its pristine state. This method sets the form's $pristine state to true, the $dirty state to false, removes the ng-dirty class and adds the ng-pristine class.


1 Answers

We do something similar to your answer, we have a formSubmitted directive that binds to the submit event, if fired we set $submitted variable on the form controller. That way you can use it in a similar fashion than how you use ShowValidationMessages but it is re-usable. Very simple directive:

app.directive('formSubmitted', [function () {
    return {
        restrict: 'A',
        require: 'form',
        link: function (scope, element, attrs, ctrl) {
            ctrl.$submitted = false;
            element.on('submit', function () {
                scope.$apply(function () {
                    ctrl.$submitted = true;
                });
            });
        }
    };
}]);

You apply this on the form tag itself as an attribute.

We took it a couple of steps further, our requirement was to show validation errors only if the following holds true: the element is invalid AND either the form was submitted OR the input element has blurred. So we ended up with another directive that requires ngModel that sets the blurred status of the element on the ngModel controller.

And finally to get rid of a whole lot of repeated boiler-plate code in html to check all these thing, e.g. your ng-show="frmMyForm.myField.$invalid && (!frmMyForm.myField.$pristine || MyObject.ShowValidationMessages)" we encapsulated that into a directive as well. This template directive wraps our input elements with Bootstrap boiler-plate as well as handle all the validation stuff. So now all my form inputs just follow this pattern:

<div data-bc-form-group data-label="Username:">
    <input type="text" id="username" name="username" ng-model="vm.username" data-bc-focus required />
</div>

and bcFormGroup directive transforms that into the following bootstrap enabled html:

<div class="form-group" ng-class="{'has-error': showFormGroupError()}" data-bc-form-group="" data-label="Username:">
    <label for="username" class="col-md-3 control-label ng-binding">Username:</label>
    <div class="col-md-9">
        <input type="text" id="username" name="username" ng-model="vm.username" data-bc-focus="" required="" class="ng-pristine form-control ng-valid ng-valid-required">
        <span class="help-block ng-hide" ng-show="showRequiredError()">Required</span>
    </div>
</div>

This keep things DRY and offers great flexibility into what type of inputs are supported.

Update:

Here is a basic listing of bcFormGroup directive. The default template uses bootstrap's horizontal form, but can be adapted to your liking.

app.directive('bcFormGroup', ['$compile', '$interpolate', function ($compile, $interpolate) {
  return {
    restrict: 'A',
    template:
        '<div class="form-group" ng-class="{\'has-error\': showFormGroupError()}">' +
            '<label for="{{inputId}}" class="col-md-3 control-label">{{label}}</label>' +
            '<div class="col-md-9">' +
                '<bc-placeholder></bc-placeholder>' +
            '</div>' +
        '</div>',
    replace: true,
    transclude: true,
    require: '^form',
    scope: {
        label: '@',
        inputTag: '@'
    },

    link: function (scope, element, attrs, formController, transcludeFn) {

        transcludeFn(function (clone) {
            var placeholder = element.find('bc-placeholder');
            placeholder.replaceWith(clone);
        });

        var inputTagType = scope.inputTag || 'input';
        var inputElement = element.find(inputTagType);
        var fqFieldName = formController.$name + '.' + inputElement.attr('name');
        var formScope = inputElement.scope();

        if (inputElement.attr('type') !== 'checkbox' && inputElement.attr('type') !== 'file') {
            inputElement.addClass('form-control');
        }

        scope.inputId = $interpolate(inputElement.attr('id'))(formScope);
        scope.hasError = false;
        scope.submitted = false;

        formScope.$watch(fqFieldName + '.$invalid', function (hasError) {
            scope.hasError = hasError;
        });

        formScope.$watch(formController.$name + '.$submitted', function (submitted) {
            scope.submitted = submitted;
        });

        if (inputElement.attr('data-bc-focus') != null || inputElement.attr('bc-focus') != null) {
            scope.hasBlurred = false;
            formScope.$watch(fqFieldName + '.$hasBlurred', function (hasBlurred) {
                scope.hasBlurred = hasBlurred;
            });
        }

        if (inputElement.attr('required')) {
            scope.hasRequiredError = false;
            formScope.$watch(fqFieldName + '.$error.required', function (required) {
                scope.hasRequiredError = required;
            });
            inputElement.after($compile('<span class="help-block" ng-show="showRequiredError()">Required</span>')(scope));
        }

        if (inputElement.attr('type') === 'email') {
            scope.hasEmailError = false;
            formScope.$watch(fqFieldName + '.$error.email', function (emailError) {
                scope.hasEmailError = emailError;
            });
            inputElement.after($compile('<span class="help-block" ng-show="showEmailError()">Invalid email address</span>')(scope));
        }

        scope.showFormGroupError = function () {
            return scope.hasError && (scope.submitted || (scope.hasBlurred === true));
        };

        scope.showRequiredError = function () {
            return scope.hasRequiredError && (scope.submitted || (scope.hasBlurred === true));
        };

        scope.showEmailError = function () {
            return scope.hasEmailError && (scope.submitted || (scope.hasBlurred === true));
        };

    }
  };
}]);

Update:

The following directive sets $focused and $hasBlurred:

app.directive('bcFocus', [function () {
    var focusClass = 'bc-focused';
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attrs, ctrl) {
            ctrl.$focused = false;
            ctrl.$hasBlurred = false;
            element.on('focus', function () {
                element.addClass(focusClass);
                var phase = scope.$root.$$phase;
                if (phase == '$apply' || phase == '$digest') {
                    ctrl.$focused = true;
                } else {
                    scope.$apply(function () {
                        ctrl.$focused = true;
                    });
                }
            }).on('blur', function () {
                element.removeClass(focusClass);
                var phase = scope.$root.$$phase;
                if (phase == '$apply' || phase == '$digest') {
                    ctrl.$focused = false;
                    ctrl.$hasBlurred = true;
                } else {
                    scope.$apply(function () {
                        ctrl.$focused = false;
                        ctrl.$hasBlurred = true;
                    });
                }
            });
        }
    };
}]);
like image 95
Beyers Avatar answered Nov 08 '22 10:11

Beyers