Search code examples
javascriptangularjsvalidationasynchronousremote-validation

Asynchronous Multifield Validation operating on a state 'just before' what I want to validate


For a project I'm working on, I've got a very simple dialog, to add a custom employee to a business application. The modal itself is nothing special - first name, last name, OK, Cancel. Easy...right?

One of the business rules, however, is that we disallow the submission of duplicate employees. To that end, I implemented the following angular directive:

(function() {
    'use strict';

    angular.module('app').directive('checkDuplicateEmployee', ['$q', 'officesSvc', 'webDataSvc', directive]);

    function directive($q, officesSvc, webDataSvc) {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function(scope, elem, attrs, vm) {
                vm.$asyncValidators.duplicateEmployee = function (modelValue, viewValue) {
                    // Problem visible here!
                    var args = {
                        officeType: officesSvc.officeType,
                        officeName: officesSvc.selectedOffice.name,
                        firstName: scope.vm.firstName,
                        lastName: scope.vm.lastName
                    };

                    // Custom wrapper for $http.
                    return webDataSvc.get('/api/admin/DoesEmployeeExist', args)
                        .then(function(results) {
                            if (results === true)
                                deferred.reject(results);

                                return true;
                            });
                };
            }
        }
    }
})();

The problem I'm encountering, is that when I add the directive to a input...

<input type="text" id="firstName" name="firstName"
       maxlength="35"
       ng-model="vm.firstName"
       required check-duplicate-employee />

And I enter something, the state that gets sent to the server is the state immediately before my keypress.

Field:      | vm.firstName:      
----------------------------
T           |
Te          | T
Tes         | Te
Test        | Tes

As a result, I can reason from this that the scope doesn't get updated until after the validation runs.

Question: How can I do an asynchronous validation that operates after the $scope.vm object has been updated? Bear in mind, I can't just pass the viewValue to the firstName property in the args list - I have this same validation on the inputs for the vm.firstName and vm.lastName properties!


Solution

  • As luck would have it, there is a way to get to the view values!

    The way angular form validation works is that it sticks things on the scope. Among the things stuck on the scope are the controllerAs alias, but also the form object. Assuming I've named my form, employeeModal, this means I can access it from a link(scope) function by calling scope.employeeForm.

    Forms have properties for each named input added them (e.g. scope.employeeForm.firstName). And, these have a $viewValue property that lets you interact with the view value.

    Thus, some slight modifications to the directive are what's needed. Specifically, one needs to A) find the name of the form object, then B) crawl the scope down to the desired element's $viewValue, and add it to the paramters of the remote validation.

    link: function(scope, elem, attrs, vm) {
        var formName = $(elme).parents('form').attr('name');
    
        vm.$asyncValidators.duplicateEmployee = function () {
            var args = {
                officeType: officesSvc.officeType,
                officeName: officesSvc.officeName,
                firstName: scope[formName].firstName.$viewValue,
                lastName: scope[formName].lastName.$viewValue
            };
    
            // Make remote validation call.
        };
    }