Wednesday, August 10, 2016

Asynchronous Validation In Angular 1.3*

Problem Introduction

One of Angular 1.3*'s more powerful features is its concept of directives. Directives are, in my opinion, what separate Angular from other frameworks and also contributes to its ease of adoption.

If you have worked with HTML at all then you've applied attributes to HTML elements to alter appearance and/or behavior in some way. Why not augment that with the ability to implement custom attributes?

Why Fix This Now?

The need for an application domain specific, server-based auto-complete/type-ahead text input widget crossed an internal, totally subjective threshold of mine. That threshold, N=3, is the point at which I'd like to start consolidating those 3 separate instances into a reusable component because N=4 is rarely far behind N=3 whereas sometimes N=2 is a fluke...

From a framework agnostic perspective this involves providing the user with a textbox for input then, as they type, querying the server and presenting matching options.

How Can Angular Help?

Angular's facility for handling this is its validation pipeline. While it could be done synchronously part of the rationale for an async-heavy framework like Angular is raising the level of interactivity of web applications.

Client side UI frameworks have had support for the behavior for ages (e.g., a ComboBox).

By using the async validation pipeline the user is able to modify other input, resize the browser, etc...  while waiting for results to update. This increases the perceived interactivity of the application.

How Does Angular's Async Validation Work?

Angular's validation framework is exposed through the Form that is published onto $scope by name (e.g., $scope.myForm when the form tag's name attribute is myForm). The details are well described in the Developer Guide/Forms/Custom Validation section.

A Few Considerations To Keep In Mind

  1. Use the custom validation example as your starting point.
  2. The user should be given some indication of progress while the server-side query is executing.
    1. myForm.$pending.myCustomAsyncValidator is defined (e.g., angular.isDefined() returns true) while the operation is executing. Makes an excellent candidate for an ng-show binding.
  3. The promise returned by the custom validator is a little tricky. If the async call succeeds the then-able promise executes any chained callbacks. But it is very likely that you need to inspect the result of the call to determine if the call's input was valid.
    1. To do this, have your custom validator return $q.reject(reason) from the success callback which will trigger rejection.
  4. If validation is failed (e.g., your validator returns $q.reject(reason)) then the error is published to the form property's error object (e.g., myForm.myProperty.$error.myCustomAsyncValidator evaluates to true).
    1. myForm.$valid will also be false.
  5. If validation succeeds (e.g., your validator returns anything other than a rejected promise) then myForm.myProperty.$error.myCustomAsyncValidator evaluates to false.
    1. This doesn't mean that myForm.$valid will be true. All validators (both synchronous and asynchronous) must successfully resolve for myForm.$valid to be true.

Code Snippets


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
angular.module('myModule').directive('myCustomAsyncValidator', ['$q', function($q) {
  //  ... returns a definition object, most omitted for brevity
  return {
    link: function(scope, elm, attrs, ctrl) {
      ctrl.$asyncValidators.myCustomAsyncValidator = function(modelValue, viewValue) {
        
        // ...
        
        // promise executes the server call using viewValue as input, 
        // omitted for brevity
        
        return promise.then(function(data) {
          if (data.failsValidation) { 
            return $q.reject('input not valid');
          } else {
            return angular.copy(data);
          }
        }); // could also handle case where the server call fails to complete here...
      }
    }
  };
}]);

This directive is applied to the HTML input element as follows:

1
<input type="text" name="myProperty" ng-model="data.myModel.myProperty" my-custom-async-validator>


No comments :

Post a Comment