Introduction
In this article, we will look at how we can build data entry forms with proper validation techniques in Angular. We will also look at how the controller should handle the data entry forms and propagate the data to the respective business logic components, i.e., services.
Link to complete series:
- Angular Tutorial - Part 1: Introduction to Angular.js
- Angular Tutorial - Part 2: Understanding Modules and Controllers
- Angular Tutorial - Part 3: Understanding and Using Directives
- Angular Tutorial - Part 4: Understanding and Implementing Filters
- Angular Tutorial - Part 5: Understanding and Implementing Services
- Angular Tutorial - Part 6: Building and Validating Data Entry Forms
- Angular Tutorial - Part 7: Understanding Single Page Applications and Angular Routing
Background
Whenever we talk about data entry forms, the first thing that comes to mind is how we will be handling the user data in the business logic and how we will ensure that the proper data is being passed in the application. In this article, our main focus will be to look at how the controller should handle the data coming from views and how we can use angular ng-form
directive to create better data entry forms.
Using the Code
Let us start the discussion by building a simple form that will accept the book
as input. Later, we can use the same form for creation of a new book
entity in the system and updating and existing book
in the system.
Understanding the ngForm Directive
The above image shows the vanilla HTML needed to implement the form. We are using bootstrap for styling the form. Before we could proceed, we first need to understand how this form will behave as a directive when it is put inside an Angular application. Actually, what happens is that if we put a form
tag inside our Angular application, Angular automatically replaces the default form with its own form directive. This way, just by putting our form on the page, we have all the functionality of Angular form directive (ngForm
) available to us.
One question that often creeps up after knowing this fact is that why Angular does that and puts its own form directive instead of our vanilla HTML form. The answer to this question is that firstly, the vanilla HTML forms are designed to do a full page postback to the server whenever it is being submitted. We can prevent that ourselves using custom code but Angular directive takes care of preventing this behaviour. However, for some obscure reasons, if we still want our Angular form to perform a full page postback, that can also be done just by specifying the action attribute in our form. But perhaps, that is not needed for most client side apps.
The second reason Angular does this is to provide us loads of features that we can use in our app. One functionality is the availability of ngSubmit
and ngClick
directive. These directives can be used to wire up our functions on form submit or on click of a submit button in form. Another feature and perhaps a very useful one is that Angular provides various states for all the form elements contained in the form. These states are very helpful when it comes to validating our form before submission. We will look at these states and form submission at a later point in this article. For now, it would suffice to say that whenever we are putting a form tag on the page, we are actually using a full featured and action packed ngForm
directive.
Updating the Service
Before we could go and create the forms to create or update the book
entity, let's update our localBookService
to be able to handle the create and edit requests. The following code shows how the service implements the create and edit functions.
(function () {
var localBooksService = function () {
var nextIdForBook = 6;
var _books = [
{ ID: 1, BookName: "4 Test Books", AuthorName: "5 Test Author", ISBN: "5 TEST" },
{ ID: 2, BookName: "5 Test Books", AuthorName: "4 Test Author", ISBN: "1 TEST" },
{ ID: 3, BookName: "1 Test Books", AuthorName: "3 Test Author", ISBN: "2 TEST" },
{ ID: 4, BookName: "2 Test Books", AuthorName: "2 Test Author", ISBN: "4 TEST" },
{ ID: 5, BookName: "3 Test Books", AuthorName: "1 Test Author", ISBN: "3 TEST" }
];
var _addBook = function (book) {
book.ID = nextIdForBook;
nextIdForBook += 1;
_books.push(book);
}
var _updateBook = function (book) {
for (var i = 0; i < _books.length; ++i) {
if (_books[i].ID == book.ID) {
_books[i] = book;
}
}
}
return {
books: _books,
addBook: _addBook,
updateBook: _updateBook
};
}
angular.module('myAngularApplication').factory('localBooksService', [localBooksService]);
}());
Controller Support Create and Edit
The next thing we need to do is to have the $scope
objects to keep track of the item being added and removed from the view. We also need functions to get the value from the view and call the appropriate service function. Let's look at the controller code and try to understand what is happening.
(function () {
var booksController = function ($scope, $filter, $window, localBooksService) {
$scope.message = "List of books";
$scope.books = [];
$scope.book = {};
$scope.editBook = null;
$scope.fetchBooks = function () {
$scope.books = localBooksService.books;
}
$scope.addBook = function () {
localBooksService.addBook($scope.book);
$scope.book = {}
$scope.fetchBooks();
}
$scope.updateBook = function () {
localBooksService.updateBook($scope.editBook);
$scope.editBook = null;
$scope.fetchBooks();
}
$scope.setEditBook = function (selected) {
$scope.editBook = angular.copy(selected);
$scope.book = {}
}
$scope.cancelEdit = function () {
$scope.editBook = null;
$scope.book = {}
}
$scope.fetchBooks();
}
angular.module('myAngularApplication').controller
('booksController', ["$scope", "$filter", "$window", "localBooksService", booksController]);
}());
What we have done in our controller is that we have created a $scope.book
object that will keep track of the book that is being added from the view. This object will be bound to a form and when the form gets submitted, we will call the $scope.addBook
which in turns saves this book item into the list using our service
class.
To be able to update a book
entity, first we need an object to keep track of which book
is being updated by the user so we create $scope.editBook
. Then, we let the user an option to select which book
he wants to update and what should be done if he cancels the update. So we created the $scope.setEditBook
which will use the user selected book
as $scope.editBook
. The cancel on the other hand will reset $scope.editBook
to null
.
If we take a closer look at the $scope.setEditBook
, we can see that we are using angular.copy
method inside. The reason for doing this is that we don't want the dirty values to get into our book
list so we create a copy if the original object was selected by the user. When user chose to update, we will use this copy object to update the actual list. This approach makes it easier to not let the invalid changes propagate to the actual model. Since we are working on a copy
object, if the values are invalid, then we can choose to ignore the update or let the user know that they are invalid and need to be fixed.
Note: Having an object copy for edit operation is recommended for edit and update scenarios as it reduces the chances of having the invalid values being propagated to the server.
Now that we have all the code needed to perform the actual operations, let's get to the topic at hand and create our forms.
Creating a book Entity
Let's use the same form that we looked at earlier and use that to create a book
entity. First thing we need to do is to bind all the $scope.book
properties with the respective UI elements. Also, the submit
needs to be wired up with the addBooks
function. Let's see how this will look.
Now when we run the application, it will look like the following:
Now we have everything wired up, if we click on the create button, we should be able to add the book
to our list.
Note: There is a small problem here that the invalid book
entities will also go to the list. Even if we just hit Create without typing anything, an entry will be created. How to fix it, we will look at a bit later in the next section.
Editing an Entity
Now that we have all the edit functionality also ready in the controller and service, perhaps we should work on how to create an edit form. The first thing we need to do is to have an edit button for each book
entity so that clicking on this will indicate which entity the user wants to update. We can do this by calling our $scope.setEditBook
function. So let's do this in our ng-repeat
block.
Now whenever the user will click on this button, the selected row's entity will be set as the book
that the user wants to edit. Let us now create a form for edit. This form will be similar to the one we created for create in the sense that it should be bound to $scope.editBook
and the submission should call the updateBook
function. But we need to have a few extra things here. First thing we need to do is to keep track of the ID
. Since we are working with the copy of actual model here, we will have the ID
put in a hidden field on this form. Second thing we need to do is to have a cancel button which will reset the editBook
object and effectively resets the update request.
Now when we run this application and select an item for edit, we can see that it is being selected as the item for edit.
Now if we update the ISBN value to 1234 and hit update, we can see the changes getting effected in the list.
Now we have the forms for create and update ready. The edit still doesn't have any validations in place but we will look at the validation in the next section.
Note: In the sample application, by default, the create form is visible. The moment user selects an item to edit, the create form will go away and the edit form will become visible. We could have used the same for both create and edit but that is deliberately not being done to avoid complexity and keep the code free from digression.
Understanding Angular Validation Framework
The current implementation has the major problem that there are no validations in place. Angular provides quite a few features that makes validation very easy. Also, it works in unison with the HTML5 validation attributes so that is also a good thing. To illustrate how this validation works, let's have a very simple validation rule that all the fields are required in our application. Let's see how we can implement this in our application.
The first thing that we need to do is to let the application know that we are taking control of validation in our hands and we don't want the default HTML5 validations. This can be done by setting the novalidate
attribute on the form
tag. Also to be able to use the validations, we need to have a name
for the form
. Angular uses this name
to let us know the state of form controls.
With this small change in place, we are ready to put our validations. First to implement the required validation, let's put the required
attribute for all our inputs.
Now let's start looking at the magic Angular does. Angular has few properties associated with all the input elements which are in the ng-form
. Here are some of these properties:
$pristine
$dirty
$valid
$invalid
The $pristine
will be true
if the input value has not been changed since the page loaded. The $dirty
is exactly the opposite. It is true
when the value has been changed. $valid
is true
if the input contains a valid value and the $invalid
is the exact opposite that it contains true
then the input element contains invalid value.
Now how can we use these properties for validation. What we will do is that whenever the input contains invalid value, we will not let the user submit the form. We can do this by passing the form object in the addBooks
function and check whether it is valid or not.
$scope.addBook = function (frmAddBook) {
if (frmAddBook.$valid) {
localBooksService.addBook($scope.book);
$scope.book = {}
$scope.fetchBooks();
}
else {
alert("Invalid values. All values are required");
}
}
Now this will prevent the invalid book entry from getting saved. But it would also be nice if we could have immediate feedback when we are editing the page. So let's try to apply some CSS class on individual elements using these properties. To be able to use these properties on individual elements, each input element must have a name
and it should be bound using ng-model
. So let's try to do this for the book name
.
What we are doing here is that we are checking if the input field is dirty
, i.e., the user has started changing the values and if it's invalid
, we attach a CSS class to the element. This CSS class is simple showing a red border if the input field is invalid and has been touched.
So using these above shown properties, we can perform validations in many different ways on our form. Perhaps one idea could be to disable the submit button itself if the form values are not valid. For individual properties also, we could implement a lot of CSS based feedback mechanism using these properties.
Note: I have not implemented this CSS based feedback for all fields. It has been done on only 1 field for demonstration purposes. The validation for add and edit have been done in the controller to avoid the invalid values in the list.
Points of Interest
In this article, we talked about the basics of Angular data entry forms. We also looked at how to perform validations using Angular provided properties on form input
elements. All that we have seen is just the tip of the iceberg as there are a lot of best practices available when it comes to building data entry forms. We have also not looked at Angular UI which is another plugin that can be used to ease up the form creation using bootstrap components. The main intention of this article was to get the reader acquainted with the ngForm
directive and how we can perform data entry and validation using Angular. This has been written from a beginner's perspective. I hope this has been informative.
History
- 16th July, 2015: First version