AngularJS Form Validation on Submit: $dirty, $valid & $invalid

โšก Smart Summary

Form Validation in AngularJS checks that data entered into a form is correct and complete before it reaches the server. Four approaches are covered here, from plain HTML5 attributes through scope properties to automatic validation modules.

  • ๐Ÿงฉ HTML5 Attributes: The required attribute and the email, password and number input types deliver browser-native checks with no scripting.
  • ๐Ÿ” State Properties: AngularJS exposes $dirty, $pristine, $valid and $invalid on every named control inside a named form.
  • ๐Ÿท๏ธ Naming Requirement: Both the form and each control need a name attribute, because validation state is addressed as formName.controlName.
  • ๐Ÿšซ Suppress Native Popups: Adding novalidate to the form stops the browser intercepting submission so AngularJS can handle it.
  • ๐Ÿ’ฌ Message Display: The ngMessages module replaces stacked ng-show directives and shows one error at a time in priority order.
  • โš™๏ธ Automatic Validation: The jcs-autoValidate module generates error messages for every control without any custom markup.
  • ๐Ÿ”˜ Submission Feedback: A Ladda button applies a spinner while a submission is in flight, signalling progress to the user.

Form Validation in AngularJS

Form Validation in AngularJS is the process of ensuring whether the data entered in a form is correct and complete. When a user submits the form, validation occurs first, before the details are sent to the server. The validation process ensures, to the best possible extent, that the input fields have been filled in the right manner.

In a real-world example, assume a site that requires a registration form to be completed before granting full access. The registration page would have input fields for username, password, email id and so forth.

For example, the email id always needs to be in the format username@site.domain. If someone enters just the username, the validation should fail. Validation performs these basic checks before the details are sent to the server for further processing.

โš ๏ธ Version note: AngularJS (the 1.x branch) reached end of life on 31 December 2021 and receives no further security patches. The techniques below remain correct for legacy applications; modern Angular handles the same job with template-driven or reactive forms.

Form validation using HTML5

Form validation is the process of pre-validating information entered on a web form before it is sent to the server. Validating on the client side is preferable, because it adds far less overhead than presenting the form again after a round trip.

The example below shows a simple registration form in which the user enters a username, password, email id and age. The form carries validation controls that ensure the information is entered properly.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Event Registration</title>
    <link rel="stylesheet" href="lib/bootstrap.css">
    <script src="https://code.angularjs.org/1.8.3/angular.min.js"></script>
</head>
<body ng-app="sampleApp">
<h1>Guru99 Global Event</h1>

<form>
    Enter your user name:
    <input type="text" name="name" required><br><br>

    Enter your password:
    <input type="password" name="password"><br><br>

    Enter your email:
    <input type="email" name="email"><br><br>

    Enter your age:
    <input type="number" name="age"><br><br>

    <input type="submit" value="Submit">
</form>
</body>
</html>

โš ๏ธ Corrections applied to every listing on this page. The originals declared chrset="UTF 8", which is neither a valid attribute nor a valid encoding, and loaded AngularJS three times over โ€” angular-route.js first, which throws because the route module registers against a framework that has not loaded, then angular.min.js and angular.js, the same library twice. They also pulled bootstrap.css through a <script> tag, so no styling was ever applied. Each listing now loads one library, version 1.8.3, and links the stylesheet correctly. Layout padding built from repeated &nbsp; entities has been removed.

Code Explanation

  1. For the text input type, the required attribute means the textbox cannot be empty when the form is submitted.
  2. The next input type is password. Because the type is marked as password, text entered in the field is masked.
  3. Because the input type is specified as email, the text in the box must match the pattern name@site.domain.
  4. When the input type is marked as number, characters typed from the keyboard are simply not accepted by the field.

Output

To see the validation in action, click Submit without entering anything.

A pop-up appears reporting that the field needs to be filled, so the control marked as required blocks submission when it is left empty.

Entering a value in the password control shows the ‘*’ symbol masking the characters.

Entering a malformed email id and clicking Submit raises a pop-up asking for the @ symbol. Finally, characters typed into the age control never appear, because the field accepts numbers only.

Browser pop-ups are quick to set up, but their wording and styling cannot be changed. That is where the AngularJS validation properties take over.

Form validation using $dirty, $valid, $invalid, $pristine

AngularJS supplies its own properties for validation. The following properties are exposed on every named control:

  • $dirty โ€“ The user has interacted with the control
  • $valid โ€“ The field content is valid
  • $invalid โ€“ The field content is invalid
  • $pristine โ€“ The user has not interacted with the control as yet

Below are the steps to carry out AngularJS validation.

Step 1) Use the novalidate property when declaring the form. This tells the browser that validation will be handled by AngularJS.

Step 2) Ensure the form has a name defined for it, because that name is used to reach the validation state.

Step 3) Ensure each control also has a name attribute, for the same reason. A control without a name is never registered on the form.

Step 4) Use the ng-show directive to check the $dirty, $invalid and $valid properties.

The example below has a single text field in which the user enters a Topic name. If it is left blank, a validation error is triggered and the message is shown.

<form ng-app="DemoApp" ng-controller="DemoController"
      name="myForm" novalidate>
    <p>Topic Name:<br>
        <input type="text" name="user" ng-model="user" required>

        <span style="color:red"
              ng-show="myForm.user.$dirty && myForm.user.$invalid">
            <span ng-show="myForm.user.$error.required">Username is required</span>
        </span>
    </p>
    <p>
        <input type="submit" ng-disabled="myForm.$invalid">
    </p>
</form>

<script>
    var app = angular.module("DemoApp", []);

    app.controller("DemoController", function($scope) {
        // Seed the textbox so the form starts in a valid state
        $scope.user = "AngularJS";
    });
</script>

โš ๏ธ Two logic errors corrected. The original disabled the button with ng-disabled="myForm.user.$dirty && myForm.user.$invalid". Because $dirty is false until the user touches the field, the button stayed enabled on an untouched empty form, the exact case it was meant to block; myForm.$invalid covers both. The original also wrapped the default value in $scope.Display = function () { $scope.user = 'Angular'; }, a function nothing ever called, so the textbox opened empty despite the text claiming it showed “AngularJS”. The assignment is now made directly, with the value the surrounding text quotes.

Code Explanation

  1. The form is given the name “myForm”. This is required in order to reach its controls for validation.
  2. The novalidate property stops the browser validating, so AngularJS can do it instead.
  3. The ng-show directive checks the “$dirty” and “$invalid” properties. If the textbox has been modified, $dirty is true; if its value is empty, $invalid is true. When both are true the span with the red text is displayed.
  4. The nested check reads the “$error.required” property, which is true whenever a required control has no value, and displays “Username is required”.
  5. The submit button is disabled through ng-disabled whenever the form as a whole is invalid.
  6. The controller sets the initial textbox value to “AngularJS”, so the form opens in a valid state and the validation is easier to observe as the text is deleted.

Output

When the form is first displayed, the textbox shows “AngularJS” and the submit button is enabled. As soon as the text is removed, the error message appears and the button is disabled.

The screenshot above shows two things:

  • The submit button is disabled
  • There is no topic name in the Topic textbox, so the error message “Username is required” is shown

How to Show Validation Errors with ngMessages

The pattern above works, but it does not scale. A field with four constraints needs four nested ng-show spans, and when several of them fail at once every message appears together. The ngMessages module, shipped separately by the AngularJS team, solves both problems: it shows one message at a time, in the order the messages are written.

Load the module and add it as a dependency.

<script src="https://code.angularjs.org/1.8.3/angular-messages.js"></script>
<script>
    angular.module("DemoApp", ["ngMessages"]);
</script>

The markup then reads as a list of rules rather than a stack of conditions.

<input type="text" name="user" ng-model="user"
       required ng-minlength="4" ng-maxlength="20">

<div style="color:red" ng-messages="myForm.user.$error"
     ng-if="myForm.user.$dirty">
    <div ng-message="required">A topic name is required</div>
    <div ng-message="minlength">Use at least 4 characters</div>
    <div ng-message="maxlength">Use no more than 20 characters</div>
</div>

Three details make this behave well in practice. The ng-messages attribute points at the control’s $error object rather than at a boolean, so no condition has to be repeated. Only the first matching ng-message renders, which means a blank field reports that it is required instead of also complaining about its length. Adding ng-messages-multiple reverses that behaviour when every failure genuinely needs to be listed.

Wrapping the block in ng-if="myForm.user.$dirty" keeps the form quiet until the user has actually typed something. Substituting $touched for $dirty delays the message further, until focus leaves the field, which is usually the friendlier choice on a long registration form.

One caveat: ngMessages ships as a separate file, so forgetting the script tag or the module dependency leaves the block rendering nothing at all, with no console error to explain it.

Writing messages by hand is precise but repetitive. The next section covers a module that generates them automatically.

Form validation using AngularJS Auto Validate

AngularJS can validate all controls on a form automatically, without custom code for the validation or the error messages. This is done by including a community module called “jcs-autoValidate”.

With this module in place, no special code is needed to run the validation or display the messages; everything is handled inside jcs-autoValidate.

The example below has a simple form with a single required textbox. An error message is displayed if the control is not filled in.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Event Registration</title>
    <link rel="stylesheet" href="lib/bootstrap.css">
    <script src="https://code.angularjs.org/1.8.3/angular.min.js"></script>
    <script src="lib/jcs-auto-validate.min.js"></script>
</head>
<body>
<h1>Guru99 Event</h1>

<div ng-app="DemoApp">
    <form name="myForm" novalidate>
        <div class="form-group">
            <label for="user">Topic Name:</label>
            <input class="form-control" type="text"
                   id="user" name="user" ng-model="user" required>
        </div>
        <div class="form-group">
            <input type="submit">
        </div>
    </form>
</div>

<script>
    var app = angular.module('DemoApp', ['jcs-autoValidate']);
</script>
</body>
</html>

โš ๏ธ Correction: the original identified the control with id="user" only. jcs-autoValidate reaches the control through the form, so a name attribute is mandatory and the message never appeared without it. The original also loaded two different AngularJS versions, 1.6.4 from the Google CDN alongside 1.6.9, and never linked Bootstrap even though the example depends on its form-group and form-control classes to position the error text.

๐Ÿ’ก Note: jcs-autoValidate is a community project and is no longer actively maintained. It remains usable on existing AngularJS applications, but the built-in ngMessages approach shown earlier is the safer choice for anything new.

Code Explanation

  1. The “jcs-auto-validate.js” script, which holds all the auto-validation functionality, is included.
  2. Each control and its surrounding div are placed inside a “form-group” class, which is where the module injects its message.
  3. The control carries both an id and a name, and Bootstrap supplies the form-control styling.
  4. The “jcs-autoValidate” module is listed as a dependency of the AngularJS module.

Output

By default the form is rendered exactly as written.

Submitting the empty form displays the message “This field is required”, generated entirely by jcs-autoValidate. Once the form validates, the remaining question is what the user sees while it is being sent.

User feedbacks with Ladda buttons

The “Ladda” buttons are a framework built on top of JavaScript that gives buttons a visual effect when they are pressed.

If a button is given the “ladda” attribute and pressed, a spin effect is shown. Different data styles are available for additional visual effects.

The example below shows a simple form with a submit button. When the button is pressed, a spin effect appears on it.

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Event Registration</title>
    <link rel="stylesheet" href="lib/bootstrap.css">
    <script src="https://code.angularjs.org/1.8.3/angular.min.js"></script>
    <script src="lib/angular-ladda.min.js"></script>
</head>
<body>
<h1>Guru99 Event</h1>

<div ng-app="DemoApp" ng-controller="DemoController">
    <form name="myForm" novalidate ng-submit="submit()">
        <button class="btn btn-primary" type="submit"
                ladda="submitting" name="sbutton"
                data-style="expand-right">Submit</button>
    </form>
</div>

<script>
    var app = angular.module('DemoApp', ['angular-ladda']);

    app.controller('DemoController', function($scope) {
        $scope.submitting = false;

        $scope.submit = function() {
            $scope.submitting = true;
        };
    });
</script>
</body>
</html>

โš ๏ธ Correction: the original loaded angular-ladda.js and angular-ladda.min.js together, which registers the same module twice, and again pulled in two AngularJS versions. It also listed jcs-autoValidate as a dependency that this example never uses. One copy of each library is loaded here.

Code Explanation

  1. The “ng-submit” directive calls a function named “submit”, which changes the ladda attribute of the button.
  2. The ladda attribute belongs to the Ladda framework and is what adds the spin effect. Its value is bound to the submitting variable.
  3. The data-style property is another Ladda attribute that selects a different visual effect.
  4. The ‘angular-ladda’ module must be added to the AngularJS application for the framework to work.
  5. The submitting variable is initialised to false, so the button starts without the spin effect.
  6. The submit function sets submitting to true, which applies the effect. In a real application it would be reset to false once the server responds, otherwise the button spins forever.

Output

When the form is first displayed, the submit button is shown in its plain form.

When the button is pressed, the submitting variable is set to true. That value reaches the “ladda” attribute of the button, producing the spin effect.

FAQs

$dirty becomes true when the value changes. $touched becomes true when the field loses focus. Use $touched to delay an error until the user has moved on, which feels less aggressive while typing.

Add the ng-pattern directive with a regular expression, for example ng-pattern=”/^[0-9]{6}$/”. The failure then surfaces as $error.pattern, which ngMessages can report like any built-in constraint.

Yes. AI tools read the markup and produce matching required, ng-pattern and ngMessages blocks. Always review the regular expressions, since generated email and phone patterns often reject valid input.

AI assistants check the three usual causes quickly: a missing name attribute on the form or control, a missing ng-model, and novalidate left off so the browser intercepts submission first.

No. It improves the experience and reduces round trips, but anyone can bypass it with developer tools. Every rule enforced in the browser must be enforced again on the server.

Summarize this post with: