AngularJS provides a small and well-defined set of constructs that make standard form-based operations easier. For a form, we should consider three points:
- Allowing user input
- Validating those inputs against business rules
- Submitting the data to the backend server
form in AngularJS is extend from html
form. Now it’s a directive. When Angular encounters the
form tag, it executes the
form directive. This directive creates an instance of a special Angular class
FormController that is made available to us on the current scope. It’s this controller which provides an API to check and manipulate the state of the form. The same as
ng-model which creates an instance of
It already has built-in support for validating. Validations are automatically setup by AngularJS according to:
- Input type - text, numbers, e-mails, URLs, radios, checkboxes, and a few others. (such as
- Validation attributes -
max, and custom attributes such as
So AngularJS will check
required attribute. When input is empty, the error label will be shown.
from directive creates an instace of
FormController and published into the scope using the name attribute. Then the inside
NgModelController and published as a property of the form instance using the name attribute.
$error and $dirty is of
It contains a list of all errors for the specific ng-model directive. From above example,
required is validation attribute. So to check the validation, we need to follow this format:
When validation is failing,
$error.validation will be true. We could use it to decide whether to show error messages.
States by ng-model
$error, every element that uses ng-model — including input, textarea, and select — has states defined on the associated model controller:
- $pristine: True if user does not interact with the input. Any updates to the input field and $pristine is set to false. Once false, it never flips, unless we call the
$setPristine()function on the model controller.
- $dirty: Reverse of $pristine. This is true when the input data has been updated. This gets reset to false if
- $touched: True if the control ever had focus.
- $untouched: True if the control has never lost focus.
- $valid: True if there are validations defined on the input element and none of them are failing.
- $invalid: True if any of the validations defined on the element are failing.
So from above example:
If there is no
form.formName.$dirty, the validation message is shown as soon as we load the form. When user never interacts with the input,
$pristine is true and
$pristine is false. So here, the error message will never be shown until user begins to interact with the input.
CSS to an input element
Based on the model state, Angular also adds some CSS classes automatically to an input element.
ng-valid/ng-invalid: This is used if the model is valid or not
ng-pristine/ng-dirty: This is used if the model is pristine or ng-dirty.
ng-untouched/ng-touched: This is used when the input is never visited or not.
ng-invalid-<errorkey>/ng-valid-<errorkey>: This is used for a specific failed/sucessed validation.
ng-empty/ng-not-empty: This is used if the model is empty or not
For example, when we check the page by Inspect of browser (not your own code), we could find a list of class already added in input:
When we type something, it could change to:
So we could customize input element by these classes according to different state. For example:
Points to be careful
If data in the model is invalid, it does not show up in the view and the view element is empty.
For example, we set age value as “2” in controller in init method. But on the view, we set a “min=’5’” validation. So the input is empty when we load page.
Error messages by ng-messages
ng-message that allow us to show/hide error messages with a less verbose syntax. It’s better to use them to show validation errors instead of
To use them, we need to add angular-messages.js and add ngMessages module.
Think about a lot of validation in one input:
We could change it to:
With above example, each time, only one message will be shown. (Always the upper one has higher priority). When user typed “-1”, both
$error.pattern will be true. But only error message for
min will be shown. To avoid this problem:
Message reuse and override
Because many messages are the same in a big and complex form, we could reuse them by defining all messages in a sepereted file:
And include it by
If generic messages are not enough to match all input fields, we could override messages defined in the remote template by redefining them within the directive container.
There two ways to create a custom validation:
- By AngularJS-UI
- By our own directive
By our onw directive
Define a directive:
Add it in input element:
Form in Angular has a different role to play as compared to traditional html form that posts data to the server. We could not find
action attribute. So how to submit data?!
The standard form behavior of posting data to the server using full-page post-back does not make sense with a SPA framework such as AngularJS. In Angular, all server requests are made through AJAX invocations originating from controllers, directives, or services. While the traditional one will refresh the whole page.
So two ways to do it:
- By binding function to the button directly
Then, we need to know that from controller also has some APIs and properties as model controller:
$setValidity(validationKey, status, childController): This is similar to the
NgModelControllerbut is used to set the validation state of the model controller inside form controller.
$setDirty(): This is used to mark the form dirty.
$setPristine(): This is used to make the form pristine. This is often used to mark the form pristine after persisting the data to server. The $setPristine call propagates to all model controllers registered with the form, so all child inputs are also set back to the pristine state.
$setUntouched(): This is used to mark the form untouched. This is mostly called in sync with
$setPristine, after data is submitted.
Other than the state manipulation API, there are some handy properties:
They are similar to model controller properties except for the $error property. It’s in fact a bit more complex. It aggregates all failures across all contained inputs. The
$error’s property corresponds to the failing error condition and the value is an array of controllers that are invalid. For example, If there are three
$error.required should be an array with three controllers with this kind of error.
Any way, we use the $invalid property of the form controller to verify if there are validation errors before we perform a submit.
So submit button will only be available when no validation errors of its elements. We also use
$valid/$invalid to set css :D.
Points to be careful
In general, instead of disabling a submit button, we prefer to inform user all validation errors when user clicks submit button.
If we remove
ng-disabled, when user loads the page and clicks submit button directly without touching others. Nothing is submitted as the form is invalid, but validation errors on its child elements like input do not show up at all. Look at one label:
form.formName.$dirty disables validation messages until user has touched the element. It’s why here.
submit(), we could set a flag to fix this problem.
So now update label:
When user clicks submit button, we force validation to be true!. But still a problem here, we need to repeat this fix on all labels and a little complex to read. So why not create a function because all validations need to check
So now update label again:
For the moment, we only need to pass model controller and its validation without caring the conditions!
The standard way to reset a form is to call the reset method on the form object such as
document.forms["formName"].reset() or to use
type="reset" for a button.
But we could also define a function by ourselves to add on the reset button