< Previous chapterNext chapter >
Last updated .
I already touched on Angular's two-way data binding with ng-model on input elements in chapter 2. In this chapter I will explore in greater depth how to work with html forms and input elements in the context of Angular.js.
In addition to keeping data and view in sync through the use of binding, the ng-model directive also takes care of control validation, keeps track of the control state and updates the view by dynamically setting or removing certain css class names from the class of an input element. To experiment with this I have created a new view with a form containing three input fields. There is an input of type text, which is bound to a Name property in the model. Likewise, an input of type email is bound to an Email property and an input of type number is bound to an Age property. Notice that I set the novalidate attribute on the form tag to turn off default validation. The form resembles something that might be seen in a real CRUD app, but it doesn't do anything for real.
Form.html:
<!doctype html>
<html>
<head>
<title>Form</title>
<meta charset="utf-8" />
<script src="Scripts/Angular/angular-1.5.0-rc.0/angular.js"></script>
</head>
<body ng-app="AngularProject">
<h3>New contact</h3>
<form novalidate="novalidate" ng-controller="myFormController as vm">
<div>Name: <input ng-model="vm.NewObject.Name" required="required" /></div>
<div>Email: <input type="email" ng-model="vm.NewObject.Email" /></div>
<div>Age: <input type="number" ng-model="vm.NewObject.Age" required="required" /></div>
<pre>New: = {{vm.NewObject | json}}</pre>
</form>
<script src="Scripts/App.js"></script>
<script src="Scripts/Form/MyFormController.js"></script>
</body>
</html>
As usual, I have created a controller to back the view, this time I called the file MyFormController.cs and placed it in a new /Scripts/Form folder. The controller is really simple, it just contains an empty object, which Angular will fill up with the three properties, as the user types into the input fields:
Scripts/Form/MyFormController.js:
(
function ()
{
var app = angular.module('AngularProject');
app.controller("myFormController", MyFormController, []);
function MyFormController()
{
var vm = this;
vm.NewObject = {};
}
}
)();
(If you want to follow along, make sure you also have the App.js file ready.)
Running the example in the browser demonstrates that the model data in the controller are being updated continuously as the user types in an input field. It also shows that if the user enters invalid data in an input field, the corresponding property is removed from the model. This is somewhat surprising - the average .Net developer is not used to seeing a property on a class suddenly disappearing at runtime! An empty field which has the required attribute is also considered invalid, and the property therefore removed. But at least, it is clear that the data entered by the user are being validated. The validation takes place any time the data change, such as on a keystroke.
So far, there has been no visual clue to the user as to the validity of data. It turns out that Angular dynamically sets various css classes on input elements, reflecting the state and validity of the element:
css class | Description |
ng-valid | the model is valid |
ng-invalid | the model is invalid |
ng-valid-[key] | for each valid key added by $setValidity |
ng-invalid-[key] | for each invalid key added by $setValidity |
ng-pristine | the control hasn't been interacted with yet |
ng-dirty | the control has been interacted with |
ng-touched | the control has been blurred |
ng-untouched | the control hasn't been blurred |
ng-pending | any $asyncValidators are unfulfilled |
The idea is, of course, that we use these classes to indicate the state and validity, so let's indicate to the user if the data are valid, but only if the user has actually changed the data. This can be done by making use of the ng-dirty, ng-valid and ng-invalid css classes. Adding these classes in a style tag in the markup does the trick:
<style>
input.ng-dirty.ng-invalid { color:red; border-color:red; }
input.ng-dirty.ng-valid { color:green; border-color:green; }
</style>
In spite of the improvement with visual clues to the user, a customer would probably not be satisfied with that it is possible to submit invalid data. Okay, there is no submit button, but if there were, nothing would prevent the user from submitting the data. So, lets make a couple of improvements. First, I added a (so far useless) Submit method in the controller:
Scripts/Form/MyFormController.js:
(
function ()
{
var app = angular.module('AngularProject', []);
app.controller("myFormController", MyFormController, []);
function MyFormController()
{
var vm = this;
vm.NewObject = {};
vm.Submit = function ()
{
// Send data somewhere...
};
}
}
)();
I also added a Submit button in the markup and I assigned "myForm" to the name property of the form. This allows me to insert an ng-submit attribute on the form containing an expression to prevent form submission if the data are not valid:
Form.html:
<!doctype html>
<html>
<head>
<title>Form</title>
<meta charset="utf-8" />
<script src="Scripts/Angular/angular-1.5.0-rc.0/angular.js"></script>
<style>
input.ng-dirty.ng-invalid { color:red; border-color:red; }
input.ng-dirty.ng-valid { color:green; border-color:green; }
</style>
</head>
<body ng-app="AngularProject">
<h3>New contact</h3>
<form novalidate="novalidate" ng-controller="myFormController as vm"
name="vm.myForm" ng-submit="vm.Submit()">
<div>Name: <input ng-model="vm.NewObject.Name" /></div>
<div>Email: <input type="email" ng-model="vm.NewObject.Email" /></div>
<div>
Age: <input type="number" required="required" ng-model="vm.NewObject.Age" name="Age" />
<span ng-show="vm.myForm.Age.$error.required">Enter your age!</span>
<span ng-show="vm.myForm.Age.$error.number">Not valid!</span>
</div>
<div ng-show="vm.myForm.$submitted">Thank you for submitting!</div>
<div ng-show="vm.myForm.$invalid">Something wrong!</div>
<input type="submit" ng-disabled="vm.myForm.$invalid" value="Submit" />
<pre>New: = {{vm.NewObject | json}}</pre>
</form>
<script src="Scripts/App.js"></script>
<script src="Scripts/Form/MyFormController.js"></script>
</body>
</html>
Now the ng-submit directive contains script to call the Submit method of the controller. Also, the Submit button is disabled, as long as the form is not valid, ensuring that only valid data are submitted. There are a few things to take in from this example, which are summarized here:
ng-show: This directive can be used to show or hide an element, based on a boolean expression. Angular hides an element by setting a css class on the element. This css class is built-in to Angular (you don't have to define it yourself in the css). In this example, the directive is used to selectively show or hide some messages, based on various boolean flags on the form object.
name on form: Setting a name on a form is nothing new, but in this case it allows one to reference various properties of the form and its input elements. Setting the name actually causes Angular to associate a FormController with the form, which is then also callable from within the controller. Note that the name is prefixed with the name of the controller (in this case "vm").
ng-submit: This directive exists to contain an expression of the action to submit the form. It is actually called in response to a onsubmit event.
$error: Is an object with boolean properties for all the possible validation error types, such as required, date and email. A property is true when the validation fails.
$submitted: Is a boolean property on the FormController set to true when the form has been submitted.
$invalid: Is a boolean property on the FormController set to true when the form, or one or more input elements, is invalid.
ng-disabled: This directive sets the disabled attribute on an element, based on a boolean expression. In this example it is used to disable the Submit button when the form is invalid.
Angular comes with support for most types of validations, including validation with regular expressions using the ng-pattern attribute. In the current example, the "Age" field is configured as of type number, but that allows fractional numbers, which is not good. In addition, it would be sensible to limit the range to between 0 and, say, 140 years of age. The requirement that only integers should be allowed can be solved with a regular expression, but then there is no range check. This dilemma can be solved by defining a custom integer type of validation.
An input control that has the ngModel directive holds an instance of NgModelController, which has a $validators object with named validation functions that we can add to. Such a function will receive modelValue (the original value) and the viewValue (the current value) as parameters. Once a validation function has been added to the $validators object, it will get called by Angular, just as with any other type of validation. To add a new type of validation, a new directive must be defined and added to the module. This is done with the module.directive function. I decided to call the new constraint "integer" and define it in a separate file:
Scripts/Validators.js:
(
function ()
{
var app = angular.module('AngularProject');
app.directive('integer', function ()
{
return {
require: 'ngModel',
link: function (scope, elm, attr, ctrl)
{
const regexp = /^\-?\d+$/;
var min = GetLimit(Number.MIN_SAFE_INTEGER, "min", attr);
var max = GetLimit(Number.MAX_SAFE_INTEGER, "max", attr);
ctrl.$validators.integer = function (modelValue, viewValue)
{
if (ctrl.$isEmpty(modelValue))
return true;
if (regexp.test(viewValue)) {
var value = Number.parseInt(viewValue);
return !isNaN(value) && value >= min && value <= max;
}
return false;
};
function GetLimit(defaultLimit, name, attr)
{
if (attr[name]) {
var value = Number.parseInt(attr[name]);
if (!isNaN(value))
return value;
}
return defaultLimit;
}
}
};
});
}
)();
The module.directive function receives as parameters the name of the directive and a function that returns an object with two properties. The require property tells Angular which services are required by the directive. The link property is a function in which the validator code goes. It receives a few arguments, of which we can use the attr argument and the ctrl argument. The former is a collection of the attributes on the input element that uses this directive, and the latter is a reference to the NgModelController attached to the element. Now, in the actual validation code, it is checked that the value consists of only digits by testing with a regular expression. Then, the range, defined by the (optional) min and max properties is also checked. If everything is well, the validation returns true.
To use this validation, i modified the markup for the "Age" field to this:
Form.html (partial):
Age: <input min="0" max="140" integer type="text" ng-model="vm.NewObject.Age" required="required" name="Age" />
<span ng-show="vm.myForm.Age.$valid">OK!</span>
<span ng-show="vm.myForm.Age.$error.required">Enter your age!</span>
<span ng-show="vm.myForm.Age.$error.integer">Not valid!</span>
...
<script src="Scripts/App.js"></script>
<script src="Scripts/Validators.js"></script>
<script src="Scripts/Form/MyFormController.js"></script>
The input field now uses the new integer directive (attribute) and also min and max constraints. In addition, the ng-show directive in the last line now also references the new directive to produce the text "Not valid!" if the validation fails.
And it actually works, as shown here:
As an aside, you may be wondering why I validate using both a regular expression and try to parse the value to an int. Well, it is because Number.parseInt
will happily parse a value such as "12abc", giving me the number 12. Using the regular expression ensures that the value is only digits.
module.directive: This function is used to add a custom directive, such as a validation directive, to a module. Pass the name of the directive in the first parameter. The second parameter takes a factory function that creates the directive.
$validators: An element decorated with an ng-model directive is associated with an ngModelController instance, which in turn has a $validators property, which is an object with named validator functions.
< Previous chapterNext : Navigation on the client side >
2016 by Niels Hede Pedersen