I need to be able to temporarily persist data that isn\'t fully validated yet, then enforce validation when I\'m ready to make it permanent. But Angular is preventing that.
I took a stab at doing this. The basic idea is to keep the model updated whether or not the input is valid by grabbing the text right off the input element. Then, updating the view an $render
with the model data, even if the view is undefined
. I haven't gotten the from to change the style on load with bad data. I'm sure that's just a matter of calling $setViewValue()
with something invalid then updating the element.
It's not a terribly angular way to do things. If you needed an invalid version of the form, then I might used a directive to bind think my-model-no-validation="myDirtyModel.value"
, but that's a task for another day.
Here is a fiddle: http://jsfiddle.net/fooby12/dqPbz/
Shortened code for directive:
angular.module('bindApp', []).
controller('bindToViewCtrl', function($scope) {
$scope.zip = /(^\d{5}$)|(^\d{5}-\d{4}$)/;
$scope.formData = {
zip: '1234',
bindToView: true
};
}).
directive('bindToView', function($log) {
return {
require: 'ngModel',
scope: {bindToViewIsOn: '&bindToView', ngModel: '='},
link: function (scope, iElem, iAttr, ngModel) {
if(!ngModel) return;
ngModel.$render = function() {
iElem[0].value = ngModel.$viewValue || ngModel.$modelValue;
};
iElem.on('blur keyup change', function() {
scope.$apply(updateModel);
});
scope.$watch(function() { return scope.bindToViewIsOn(); }, function() {
updateModel();
});
function updateModel(){
if(scope.bindToViewIsOn()){
scope.ngModel = iElem[0].value;
}
}
}
};
});
Example HTML:
<div ng-app="bindApp" ng-controller="bindToViewCtrl">
<form name="bindForm">
<label> Zip Code
<input type="text" ng-pattern="zip" required ng-model="formData.zip" bind-to-view="formData.bindToView" name="zipCode"/>
</label>
<span>$scope.formData.zip: {{formData.zip}}</span>
<br/>
<button ng-click="formData.bindToView = !formData.bindToView">
Bind to View is {{formData.bindToView ? 'On' : 'Off' }}
</button>
</form>
</div>
make sure you are injecting $scope on that controller initialization, because i got same issue with typeahead auto complete, i fix this issue by set validity on saving as below:
if (!self.editForm.$valid && self.editForm.txtCustomer.$invalid) {//workaround to fix typeahead validation issue.
self.editForm.txtCustomer.$setValidity('editable', true);
}
Emil van Galen has a blog post that covers exactly this issue. I've used his input directive, which works perfectly.
As he point out, The $parsers array of NgModelController is:
Array of functions to execute, as a pipeline, whenever the control reads value from the DOM. Each function is called, in turn, passing the value through to the next. Used to sanitize / convert the value as well as validation. For validation, the parsers should update the validity state using $setValidity(), and return undefined for invalid values.
So, to allow the model to be updated to an invalid value, yet retain the validation results, create a directive that does not return undefined
for invalid values. For example, Emil's directive reverts invalid, undefined string values to the model value, otherwise it returns the view value:
angular.module('jdFixInvalidValueFormatting', [])
.directive('input', function() {
return {
require: '?ngModel',
restrict: 'E',
link: function($scope, $element, $attrs, ngModelController) {
var inputType = angular.lowercase($attrs.type);
if (!ngModelController || inputType === 'radio' ||
inputType === 'checkbox') {
return;
}
ngModelController.$formatters.unshift(function(value) {
if (ngModelController.$invalid && angular.isUndefined(value)
&& typeof ngModelController.$modelValue === 'string') {
return ngModelController.$modelValue;
} else {
return value;
}
});
}
};
});
You can see it working in his Plunker (also note his take on an improved handling of null
rather than undefined
): http://plnkr.co/edit/gist:6674554?p=preview
The following solution can be used since Angular version 1.3:
You can set ng-model-options="{ allowInvalid: true }"
in the input field of the model where you want to persist invalid attributes.
allowInvalid: boolean value which indicates that the model can be set with values that did not validate correctly instead of the default behavior of setting the model to undefined
https://docs.angularjs.org/api/ng/directive/ngModelOptions
Then, when you are ready to show the user their validation errors, you are free to do it your way. Just remember to give your inputs and forms name
attributes, so that you can reference them in your scope.
E.g. if($scope.myFormName.my_input_name.$invalid) { ... }
A relevant tutorial: http://blog.thoughtram.io/angularjs/2014/10/19/exploring-angular-1.3-ng-model-options.html