Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

angularjs $setValidity not working while checking for email uniqueness

Tags:

angularjs

I use a directive to check if the email is already registered in my DB.

app.directive('uniqueEmail', function($http) {
    var toId;
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, elem, attr, ctrl) { 
        //when the scope changes, check the email.
        scope.$watch(attr.ngModel, function(value) {
                // if there was a previous attempt, stop it.
                if(toId) clearTimeout(toId);
                // start a new attempt with a delay to keep it from
                // getting too "chatty".
            toId = setTimeout(function(){
                // call to some API that returns { isValid: true } or { isValid: false }
                    $http.get('check/e/' + value).success(function(data) {
                    //set the validity of the field
                        ctrl.$setValidity("uniqueEmail", data.isValid);
                    });
                }, 200);
        })
        }
    }
}); 

and my input :

<form name="myForm">    
<input type="email" ng-model="userEmail" name="userEmail" required unique-email/>
<tt>myForm.userEmail.$error = {{myForm.userEmail.$error}}</tt>
</form>

My problem is that the $error.uniqueEmail is always false. the $http response shows false when email is available and true when email is already registered. (adding alert(data.isValid) confirms that.

I also tryed to switch the true/false statement from the $http reponse without sucess (email already registered = false and email available = true).

What am i missing ?

Edit : Ok i found the problem

  1. ctrl.$setValidity("uniqueemail", false) shows true and ctrl.$setValidity("uniqueemail", true) shows false
  2. the reponse of the http was {"isValid":"true"} and it must be {"isValid":true} (whithout quotes)
  3. I switched the true/false statement from the http response, so when an error has to be triggered (email already registered) the response must be {"isValid":false}
like image 686
Bobby Shark Avatar asked Apr 11 '14 11:04

Bobby Shark


2 Answers

No need for a regexp at all if you use a type="email". Angular will care about the validation for you.

angular.module('directives.emailUnique', [])
    .directive('emailUnique', ['$http', function ($http) {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, el, attrs, ctrl) {

                ctrl.$parsers.push(function (viewValue) {
                    if (!viewValue) {
                        ctrl.$setValidity('emailUnique', true);
                        return undefined;
                    }
                    $http.get('/api/emailunique/' + viewValue).success(function (data) {
                        if (data.data && data.data.unique)
                            ctrl.$setValidity('emailUnique', true);
                        else
                            ctrl.$setValidity('emailUnique', false);
                    }).error(function () {
                            alert('Sorry, a technical issue prevents to validate your email.\n ' +
                                'Thanks to retry later.');
                        });
                    return viewValue;
                });
            }
        };
    }]);

Achieving purely practicing TDD to handle every cases, here's the spec:

describe('emailUnique directive', function () {
    var $scope, form, $httpBackend;

    beforeEach(function () {
        module('directives.emailUnique');
        inject(function ($compile, $rootScope, _$httpBackend_) {
            $scope = $rootScope;
            $httpBackend = _$httpBackend_;
            var element = angular.element(
                '<form name="form" novalidate><input name="email" type="email" ng-model="model.email" required email-unique/></form>'
            );
            $scope.model = {
                email: ''
            };
            $compile(element)($scope);
            $scope.$digest();
            form = $scope.form;
        });
    });

    afterEach(function () {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });

    describe('email uniqueness', function () {
        it('should return the current value if the email is a valid one', function () {
            $httpBackend.whenGET('/api/emailunique/[email protected]').respond(200, {data: {unique: true}});
            setViewValue('[email protected]');
            $httpBackend.flush();
            expect($scope.model.email).toBe('[email protected]');
            $httpBackend.whenGET('/api/emailunique/[email protected]').respond(200, {data: {unique: true}});
            setViewValue('[email protected]');
            $httpBackend.flush();
            expect($scope.model.email).toBe('[email protected]');
        });
        it('emailUnique validity should not be evaluated to true if email is invalid', function () {
            setViewValue('michael');
            expect(form.email.$error.emailUnique).not.toBe(true);
        });
        it('should set emailUnique validity to true if email is unique', function () {
            $httpBackend.whenGET('/api/emailunique/[email protected]').respond(200, {data: {unique: true}});
            setViewValue('[email protected]');
            $httpBackend.flush();
            expect(form.email.$error.emailUnique).toBe(false);
        });
        it('should not set emailUnique validity to true if email is not unique', function(){
            $httpBackend.whenGET('/api/emailunique/[email protected]').respond(200, {data: {unique: false}});
            setViewValue('[email protected]');
            $httpBackend.flush();
            expect(form.email.$error.emailUnique).toBe(true);
        });
        it('should call emailUnique server api if email is valid, to check for uniqueness', function(){
            $httpBackend.expectGET('/api/emailunique/[email protected]').respond(200, {data: {unique: true}});
            setViewValue('[email protected]');
            $httpBackend.flush();
        });
        it('should set emailUnique to true if ' +
            'it was first evaluated as not unique and then back to an invalid one', function(){
            $httpBackend.whenGET('/api/emailunique/[email protected]').respond(200, {data: {unique: false}});
            setViewValue('[email protected]');
            $httpBackend.flush();
            setViewValue('michaelgmail.com');
            expect(form.email.$error.emailUnique).toBe(false);
        });
    });

    function setViewValue(email) {
        form.email.$setViewValue(email);
        $scope.$digest();
    }
});
like image 63
Mik378 Avatar answered Nov 15 '22 03:11

Mik378


You could use some directive like this also

   app.directive('uniqueEmail', ["$http",
    function($http) {
        return {
            require: 'ngModel',
            restrict: 'A',
            link: function(scope, elem, attrs, ctrl) {
                /*We need to check that the value is different to the original*/
                /*using push() here to run it as the last parser, after we are sure that other validators were run*/
                ctrl.$parsers.push(function(viewValue) {
                    var filter = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
                    if (filter.test(viewValue)) {
                        $http.get('check/e/' + viewValue).success(function(data) {
                            //set the validity of the field
                            ctrl.$setValidity("uniqueEmail", data.isValid);
                        });
                        return viewValue;
                    }
                });
            }
        };
    }
]);
like image 39
BKM Avatar answered Nov 15 '22 04:11

BKM