Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Angular Tutorial - Part 6: Building and Validating Data Entry Forms

4.93/5 (19 votes)
16 Jul 2015CPOL11 min read 46.1K   1.4K  
How to build data entry forms with proper validation techniques in Angular

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:

  1. Angular Tutorial - Part 1: Introduction to Angular.js
  2. Angular Tutorial - Part 2: Understanding Modules and Controllers
  3. Angular Tutorial - Part 3: Understanding and Using Directives
  4. Angular Tutorial - Part 4: Understanding and Implementing Filters
  5. Angular Tutorial - Part 5: Understanding and Implementing Services
  6. Angular Tutorial - Part 6: Building and Validating Data Entry Forms
  7. 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.

Image 1

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.

JavaScript
(function () {

    var localBooksService = function () {

        // Just a temporary variable to mimic auto increment on client side
        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) {
           // A lame way of updating the book in the list
            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.

JavaScript
(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.

Image 2

Now when we run the application, it will look like the following:

Image 3

Now we have everything wired up, if we click on the create button, we should be able to add the book to our list.

Image 4

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.

Image 5

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.

Image 6

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.

Image 7

Now if we update the ISBN value to 1234 and hit update, we can see the changes getting effected in the list.

Image 8

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.

Image 9

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.

Image 10

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.

Image 11

JavaScript
$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.

Image 12

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.

Image 13

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)