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.

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 entities has been removed.
Code Explanation
- For the text input type, the
requiredattribute means the textbox cannot be empty when the form is submitted. - The next input type is password. Because the type is marked as password, text entered in the field is masked.
- Because the input type is specified as email, the text in the box must match the pattern name@site.domain.
- 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
- The form is given the name “myForm”. This is required in order to reach its controls for validation.
- The
novalidateproperty stops the browser validating, so AngularJS can do it instead. - 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.
- The nested check reads the “$error.required” property, which is true whenever a required control has no value, and displays “Username is required”.
- The submit button is disabled through
ng-disabledwhenever the form as a whole is invalid. - 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
- The “jcs-auto-validate.js” script, which holds all the auto-validation functionality, is included.
- Each control and its surrounding div are placed inside a “form-group” class, which is where the module injects its message.
- The control carries both an id and a
name, and Bootstrap supplies theform-controlstyling. - 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
- The “ng-submit” directive calls a function named “submit”, which changes the ladda attribute of the button.
- The ladda attribute belongs to the Ladda framework and is what adds the spin effect. Its value is bound to the
submittingvariable. - The data-style property is another Ladda attribute that selects a different visual effect.
- The ‘angular-ladda’ module must be added to the AngularJS application for the framework to work.
- The
submittingvariable is initialised to false, so the button starts without the spin effect. - The submit function sets
submittingto 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.














