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

Constraints and Validations in Angular and Parse.com

5.00/5 (1 vote)
19 May 2015CPOL5 min read 9.9K  
Discuss the methods of setting different constraints using Angular

Introduction

During implementing an Angular project i found Anguler provides many constraint methods, this tip discuss several of them.

Background

Most of the related user-input-datas can be collected in the HTML5 <form>.

Angular rewrites the form directive, makes it to have a holistic mechanism for validation, so that the user can be notified of invalid inputs for example. If we want to use these feature from Angular, we should take some specific attributes in side of form and its subtag.

Based on our Public Library Web Application, we take the function of Add a book as an example to introduce the Angular-way's validation.

As we said, all of the user's inputs will be compacted into <form>, here is the basic format of HTML5 form tag in Angular:

<code><form name="bookInfo" novalidate="novalidate" ng-submit="createBook()">
    ...
</form>
</code>

novalidate is used to disable browser's native form validation, because every browser may have its own way to deal with form validation. That is not what we expect.

Please note that, the form is no more the orginal HTML5 tag, it has been replaced by Angular and has an alias ngForm. Which means the following two formats are the same:

<code>1. <form name="bookInfo">...</form>
2. <ng-form name="bookInfo">...</ng-form>
</code>

form in Angular is be tracked by form.FormController, which keeps watching on the state of form, such as being valid/invalid. Besides, when the name attribute of <form> is specified, the form will be published onto the current scope under this name.

Generic Constraints and Validations

  • The form's name is "bookInfo";
  • A book have four attribute: ISBN, Title, Year and Edition. We make each as a input element to receive user’s inputs.
  • Each input has an ngModel attribute, will be controlled by Angular as a model.

We take our focus on input at this moment. Angular provides many arguments in input for data-binding, state control and validation:

  • Mandatory Value
    • Constraint: <input type="text" name="isbn" ng-model="book.isbn" ng-required="true" />
    • Validation: bookInfo.isbn.$error.required
    • Description: the input of ngModel book.isbn is required, it should not be empty, otherwise Angular will throw an error, the value of bookInfo.isbn.$error.required will be true.
  • Pattern
    • Constraint: <input type="text" name="isbn" ng-model="book.isbn" ng-pattern="/\b\d{9}(\d|X)\b/" />
    • Validation: bookInfo.isbn.$error.pattern
    • Description: the input of ngModel book.isbn must meet the /\b\d{9}(\d|X)\b/ pattern, otherwise Angular will throw an error, the value of bookInfo.isbn.$error.pattern will be true.
  • String Length
    • Constraint: <input type="text" name="title" ng-model="book.title" ng-maxlength="50" />
    • Validation: bookInfo.title.$error.maxlength
    • Description: the input’s string length of ngModel book.title must be less than 50, otherwise Angular will throw an error, the value of bookInfo.title.$error.maxlength will be true.
  • Range
    • Constraint: <input type="number" name="year" ng-model="book.year" ng-min="1459" ng-max="{{currentYear}}" />
    • Validation: bookInfo.year.$error.number && bookInfo.year.$error.min && bookInfo.year.$error.max
    • Description: the input of ngModel book.year must be a number, the number also must be between 1459 and current year (talk about this later), otherwise Angular will throw corresponded error.

Besides, to allow styling of form, ngModel will automatically adds some CSS classes:

  • ng-valid: the input is valid
  • ng-invalid: the input is invalid
  • ng-touched: the input-field has been blurred
  • ng-untouched: the input-field has not been blurred
  • ng-pending: any $asyncValidators are unfullfilled
  • ...

Furthermore, the form has also some attributes for the holistic state validation. We take most useful one $invalid as an example:

<code><form name="bookInfo" ng-submit="createBook()" novalidate="novalidate">
    ...
    <input type="submit" ng-disabled="bookInfo.$invalid" />
</form>
</code>

When all of the inputs which are belong to this form are valid, the value of bookInfo.$invalid will be false (at the sametime, bookInfo.$valid would be true), then the whole datas can be submitted.

Specific (Costom) Constraints and Validations

Generic constraint and validation can solve most of the common requirements. But when it can not settle your problems, Angular provides another way to go - Custom Directive. With a custom directive, we can add our own validation functions to the $validators or $asyncValidators object on the ngModelController.

Calculated Value

Previously we implement the year's constraint and validation. The range of max is a dynamic value that should be increated every year. If we do not want to take a task for changing the value by ourself, it's better to let the computer go this.

Simple Way

As our example, we set an expression {{currentYear}} as the value of max attribute. We know that new Date().getFullYear() can get current year’s value, so we put it to the controller and assign it to the scope like $scope.currentYear = new Date().getFullYear();. Angular will take the value to the attribute.

Another Way

The first method is easily and quickly, but it will mix the structure of our web application. A DOM’s problem should be better to be solved in template side, and this is exactly the purpose of implementing Angular directives.

The Angular directives are markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell Angular’s HTML compiler (it is like attaching event listeners to the HTML to make is interactive) to attach a specified behavior to that DOM element or even transform the DOM element and its children.

We modify a little our originally definition of HTML input-field about year:

<code><input type="number" name="year" ng-model="book.year" year-period="year-period" ng-required="true" />
</code>

Here the year-period is user-defined Angular directive. It would be implemented to constrain the book’s publish year — not before 1459 and not later than current year:

<code>app.directive('yearPeriod', function() {
  return {
    restrict: 'A',  // the directive will only match attribute name
    require: 'ngModel',  // the directive require the ng-model
    link: function(scope, element, attribute, ngModel) {  // "link" is defined by Angular to modify the DOM
      ngModel.$validators.yearPeriod = function(modelValue, viewValue) { // "$validators" is a property of ngModelController, also a collection of validators that are applied whenever the model value changes.
        var minYear = 1459;  // Minimal publish year is 1459
        var maxYear = new Date().getFullYear();  // Maximal publish year is this year
        if (modelValue < 1459 || modelValue > maxYear) {
          return false;  // return to DOM a boolean value "bookInfo.year.$error.yearPeriod === true"
        } else {
          return true;  // return to DOM a boolean value "bookInfo.year.$error.yearPeriod === false"
        }
      }
    }
  };
});
</code>

Unique Value

We store books in our web application, and each book has a mandatory value ISBN, which means in our library there should not be two or more books that have one same isbn. The ISBN should be the unique Value.

We define the input of isbn first:

<code><input type="text" name="isbn" ng-model="book.isbn" ng-pattern="/\b\d{9}(\d|X)\b/" unique-isbn="unique-isbn" ng-required="true" />
</code>

Similar like Calculated Value Directive created as above, we realize here but an asynchron XMLHTTPRequest to our Parse cloud storage, which will check the database, wheather there is a book with same ISBN or not, after user filled the ISBN input-field. A boolean value “bookInfo.isbn.$error.uniqueIsbn === true” will be returned, if the ISBN exists in the database.

<code>publicLibraryDirectives.directive('uniqueIsbn', ['$q', '$timeout', '$http', function($q, $timeout, $http) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, element, attribute, ngModel) {
      ngModel.$asyncValidators.uniqueIsbn = function(modelValue, viewValue) {
        var defer = $q.defer();  // $q.defer() is used to expose the associated Promise for signaling the successful or unsuccessful completion as well as the status of the task
        $timeout(function() {  // $timeout is Angular’s wapper for window.setTimeout
          $http({  // Send a GET request to Parse.com
            method: 'GET',
            url: 'https://api.parse.com/1/classes/Book',
            params: {
              where: {'isbn': modelValue}
            },
            headers:{
              'X-Parse-Application-Id': Application_ID,
              'X-Parse-REST-API-Key': REST_API_Key,
            }
          })
          .success(function(data, status, headers, config){  // When request is success, get response message from server
            if (data.results.length === 0) {  // If there is no same ISBN in the database, the response’s array should be null
              defer.resolve();
            } else {  // otherwise the ISBN exists
              defer.reject();
            }
          })
          .error(function(data, status, headers, config){  // The connection with database went wrong
            console.log("something went wrong...");
          });
        }, 2000);  // Set a time delay
        return defer.promise;  // return a boolean value to view side after finished the XMLHTTPRequest
      }
    }
  };
}]);
</code>

License

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