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?
ngModel usually use for input tags for bind a variable that we can change variable from controller and html page but ngBind use for display a variable in html page and we can change variable just from controller and html just show variable. Save this answer.
The ngModel directive is a directive that is used to bind the values of the HTML controls (input, select, and textarea) or any custom form controls, and stores the required user value in a variable and we can use that variable whenever we require that value. It also is used during form validations.
ngOnChanges gets called before ngOnInit and whenever a component's bound input is changed FROM THE PARENT COMPONENT. Remember that ngOnChanges is specific to bound inputs on the component. This means if you don't have any @Input properties on a child, ngOnChanges will never get called.
The ng-init Directive is used to initialize AngularJS Application data. It defines the initial value for an AngularJS application and assigns values to the variables. The ng-init directive defines initial values and variables for an AngularJS application.
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.
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;
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With