6

I created a custom validation directive and used it in a form. It can be triggered with no problem, but after the validation is triggered, I found that the model value is just lost. Say I have

ng-model="project.key" 

and after validation, project.key doesn't exist in the scope anymore. I think somehow I understood AngularJS wrong and did something wrong.

Code speaks.

Here is my html page:

 <div class="container">
    ...
    <div class="form-group"
            ng-class="{'has-error': form.key.$invalid && form.key.$dirty}">
            <label for="key" class="col-sm-2 control-label">Key</label>
            <div class="col-sm-10">
                <input type="text" class="form-control text-uppercase" name="key"
                    ng-model="project.key" ng-model-options="{ debounce: 700 }"
                    placeholder="unique key used in url"
                    my-uniquekey="vcs.stream.isProjectKeyValid" required />
                <div ng-messages="form.key.$error" ng-if="form.key.$dirty"
                    class="help-block">
                    <div ng-message="required">Project key is required.</div>
                    <div ng-message="loading">Checking if key is valid...</div>
                    <div ng-message="keyTaken">Project key already in use, please
                        use another one.</div>
                </div>
            </div>
        </div>
    <div class="col-sm-offset-5 col-sm-10">
        <br> <a href="#/" class="btn">Cancel</a>
        <button ng-click="save()" ng-disabled="form.$invalid"
            class="btn btn-primary">Save</button>
        <button ng-click="destroy()" ng-show="project.$key"
            class="btn btn-danger">Delete</button>
    </div>
</form>

And here's my directive:

    .directive('myUniquekey', function($http) {
        return {
            restrict : 'A',
            require : 'ngModel',
            link : function(scope, elem, attrs, ctrl) {
                var requestTypeValue = attrs.myUniquekey;

                ctrl.$parsers.unshift(function(viewValue) {
                    // if (viewValue == undefined || viewValue == null
                    // || viewValue == "") {
                    // ctrl.$setValidity('required', false);
                    // } else {
                    // ctrl.$setValidity('required', true);
                    // }

                    setAsLoading(true);
                    setAsValid(false);

                    $http.get('/prism-cmti/2.1', {
                        params : {
                            requestType : requestTypeValue,
                            projectKey : viewValue.toUpperCase()
                        }
                    }).success(function(data) {
                        var isValid = data.isValid;
                        if (isValid) {
                            setAsLoading(false);
                            setAsValid(true);

                        } else {
                            setAsLoading(false);
                            setAsValid(false);
                        }
                    });

                    return viewValue;
                });

                function setAsLoading(bool) {
                    ctrl.$setValidity('loading', !bool);
                }

                function setAsValid(bool) {
                    ctrl.$setValidity('keyTaken', bool);
                }

            }
        };
    });

Here's the controller for the form page:

angular.module('psm3App').controller(
        'ProjectCreateCtrl',
        [ '$scope', '$http', '$routeParams', '$location',
                function($scope, $http, $routeParams, $location) {
                    $scope.save = function() {
                            $http.post('/prism-cmti/2.1', {requestType:'vcs.stream.addProject', project:$scope.project})
                            .success(function(data) {
                                $location.path("/");
                            });
                        };
                }]);

Before this bug, somehow I need to handle the required validation in my custom validation directive too, if I don't do it, required validation would go wrong. Now I think of it, maybe the root cause of these two problems is the same: the model value is gone after my directive link function is triggered.

I'm using Angular1.3 Beta 18 BTW.

Any help is appreciated. Thanks in advance.

Update: Followed @ClarkPan's answer, I updated my code to return viewValue in ctrl.$parsers.unshift() immediately, which makes required validation works well now, so I don't need lines below any more.

        // if (viewValue == undefined || viewValue == null
                    // || viewValue == "") {
                    // ctrl.$setValidity('required', false);
                    // } else {
                    // ctrl.$setValidity('required', true);
                    // }

But the {{project.key}} still didn't get updated. Then I tried to comment out these two lines here:

                    setAsLoading(true);
                    setAsValid(false);

Model value {{project.key}} got updated. I know that if any validation fails, the model value will be cleared, but I thought

                      function(data) {
                            var isValid = data.isValid;
                            if (isValid) {
                                setAsLoading(false);
                                setAsValid(true);
                            } else {
                                setAsLoading(false);
                                setAsValid(false);
                            }
                        }

in $http.get(...).success() should be executed in $digest cycle, which means the model value should be updated.

What is wrong?

2
  • I didn't scan all your code, but if any validation for a certain field fails, the associated ng-model will be cleared. Easiest to see this behaviour by making a <input type="email" ng-model="input.something"> and a {{ input.something }}. When the input does not contain a valid email address it will clear the model value. Commented Aug 14, 2014 at 22:35
  • @null, thanks for quick reply. I know model won't get updated if validation fail. My problem is model value is not updated after validation all pass,so in my case {{project.key}} shows nothing. Commented Aug 14, 2014 at 22:45

3 Answers 3

5

This is happening because angular does not apply any change to the scope and $modelValue if there is any invalid flag set in the model. When you start the validation process, you are setting the 'keyTaken' validity flag to false. That is telling to angular not apply the value to the model. When the ajax response arrives and you set the 'keyTaken' validity flag to true, the $modelValue was already set to undefined and the property 'key' was gone. Try to keep all validity flags set to true during the ajax request. You must avoid the calls to setAsLoading(true) and setAsValid(false) before the ajax call and keep all validity flags set to true. Only after the ajax response set the validity flag.

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

2 Comments

This is true. I end up with delete "setAsLoading(true) " to make sure the value can be updated to the model.
Hello, after more than two years! I got a similar problem and after looking at the docs at docs.angularjs.org/api/ng/directive/ngModelOptions there is an allowInvalid Option which, when set to true, allows invalid values to update the model! Have fun everyone!
1

NOTE:This answer below only applies if you're using angular versions prior to 1.3 (before they introduced the $validators concept).


From my reading of your myUniqueKey directive, you want to validate the projectkey asynchronously. If that is the case, that would be your problem. ngModel's $parser/$formatter system doesn't expect asynchronous calls.

The anonymous function you used in the $parsers array does not return a value, as $http is an asynchronous method that returns a method. You'll want to return the viewValue immediately from that method.

Then in the .success callback of your $http call, you can ten set the validity and loading status. I don't recommend you try to change the viewValue (unless that is not your purpose in returning either undefined or viewValue) at this point as it will probably trigger another run of the $parsers.

So:

ctrl.$parsers.unshift(function(viewValue){
    //...omitted for clarity

    $http.get(
        //...
    ).success(function(data){
        setAsLoading(false);
        setAsValid(data.isValid);
    });

    //... 

    return viewValue;
});

8 Comments

Sounds right. I'll try tomorrow. BTW, to enable myUniqueKey directive work with required directive, should I use push instead of unshift? so that required validation would fire first? Thanks.
I think that has more to do with the priority set when you defined your directive than which side of the array you add it from
hi, I tried your solution but it still didn't work.Return 'viewValue' immediately makes 'required' validation works well now, so I can comment out 'if(viewValue==undefined || viewValue ==null||viewValue==""){....}'. But the '{{project.key}}' still doesn't get updated, always empty.
Just to try to eliminate all factors, have you tried removing all forms of validation(both required and myUniqueKey to see if {{project.key}} is set at all?
It's not set because validation fails.
|
1

If the value is not valid, by default the model will not be updated (as explained in the accepted answer), but you can make the model to be updated in any case by using allowInvalid in ng-model-options

For the input field in the question:

<input type="text" class="form-control text-uppercase" name="key"
                ng-model="project.key" ng-model-options="{ debounce: 700, 
                allowInvalid: true }"
                placeholder="unique key used in url"
                my-uniquekey="vcs.stream.isProjectKeyValid" required />

Comments

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.