4

I'm using the new AngularJS async validators feature introduced in 1.3. I have a directive that looks like this:

angular.module('app')
    .directive('usernameValidator', function(API_ENDPOINT, $http, $q, _) {
        return {
            require: 'ngModel',
            link: function($scope, element, attrs, ngModel) {
                ngModel.$asyncValidators.username = function(username) {
                    return $http.get(API_ENDPOINT.user, {userName: username})
                        .then(function(response) {
                            var username = response.data;
                            if (_.isEmpty(username)) {
                                return true;
                            } else {
                                return $q.reject(username.error);
                            }
                        }, function() {
                            return $q.reject();
                        });
                };
            }
        };
    });

I'd like to somehow get the value of username.error into the model controller scope so I can display it to the user. Displaying a static message is easy, however I want to display some of the error context information returned by the server as well.

Is there a clean way to do this or am I stuck with setting properties on the model controller?

Edit: To clarify, I am not looking for a one-off solution that just works. I intend to use this directive as a reusable, cleanly encapsulated component. This means directly writing to the surrounding scope or anything like that is probably not acceptable.

0

3 Answers 3

3
+50

the validation directive is just like any other directive, you have access to $scope, so why not set the value as it: $scope.errors.username = username.error;

angular.module('app')
    .directive('usernameValidator', function(API_ENDPOINT, $http, $q, _) {
        return {
            require: 'ngModel',
            link: function($scope, element, attrs, ngModel) {

                $scope.errors = $scope.errors | {}; //initialize it

                ngModel.$asyncValidators.username = function(username) {
                    return $http.get(API_ENDPOINT.user, {userName: username})
                        .then(function(response) {
                            var username = response.data;
                            if (_.isEmpty(username)) {
                                return true;
                            } else {
                                $scope.errors.username = username.error; //set it here
                                return $q.reject(username.error);
                            }
                        }, function() {
                            return $q.reject();
                        });
                };
            }
        };
    });

I just initialized it separately $scope.errors = $scope.errors | {}; //initialize it so that you can reuse $scope.errors object in multiple directives if you wish it

Sign up to request clarification or add additional context in comments.

5 Comments

This is a pragmatic solution, but just writing to the surrounding scope breaks encapsulation and violates the principle of least surprise. Using an isolate scope and binding the errors object explicitly into the parent scope would be much better, but unfortunately creating isolate scopes on attribute directives is not recommended because of the single-scope-per-element restriction.
you cannot have this on isolate scope, but you can still get the errors object thru attrs.. will update the answer
use $parse(attr.errors)(scope) to get the errors object
@harishr can you provide a more complete example from the HTML side of using this fix? I am having trouble with the $parse recommendation you made.
@P.Brian.Mackey if you can post a separate question, that would help in understanding the problem you are facing and then can help you with the solution
1

You can pass an empty variable, and set it on reject:

angular.module('app')
.directive('usernameValidator', function(API_ENDPOINT, $http, $q, _) {
    return {
        require: 'ngModel',
        scope: {
            errorMessage: '=usernameValidator'
         },
        link: function($scope, element, attrs, ngModel) {

            ngModel.$asyncValidators.username = function(username) {
                return $http.get(API_ENDPOINT.user, {userName: username})
                    .then(function(response) {
                        var username = response.data;
                        if (_.isEmpty(username)) {
                            return true;
                        } else {
                           //set it here
                            $scope.errorMessage = username.error; 
                            return $q.reject(username.error);
                        }
                    }, function() {
                        return $q.reject();
                    });
            };
        }
    };
});

And in your template:

<input
    ng-model="model.email"                  
    username-validation="userValidationError"
    name="email"                                
    type="text">
<p class="error" ng-if="form.email.$error">{{userValidationError}}</p>

Comments

0

Why not have a global alerts array of alerts that you can push an error onto.

<alert ng-repeat="alert in alerts" type="{{alert.type}}" close="closeAlert($index)"><span ng-class="alert.icon"></span> {{alert.msg}}</alert>

Doing this, the alert can be a success or warning or whatever. And can be called from the global scope. Which I think is good so an async task in some random place you called can place an alert into the stack. Just an idea....

1 Comment

I am looking for a more Angular-idiomatic solution that does not require attaching data to the $rootScope.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.