Angularjs Chrome autocomplete dilemma - javascript

I have a simple login form which works just peachy unless you use Chrome's auto complete feature.
If you start typing and use the auto complete feature and it auto populates your password, my angularjs model does not have any value for the password.
I tried to turn autocomplete off by setting the attribute on the form autocomplete="off" but that doesn't seem to have any effect.
How can I either:
1. Ensure that I can get the value if someone uses Chrome's auto-complete feature?
2. Disable Chrome's auto-complete feature?
<form class="form-signin" name="form" ng-submit="login()" autocomplete="off">
<h3>Login</h3>
<input type="email" name="email" class="form-control" placeholder="Email address" ng-model="user.email" required autofocus>
<input type="password" name="password" class="form-control" placeholder="Password" ng-model="user.password" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>

From the link added in the comment:Github Issue's
// Due to browsers issue, it's impossible to detect without a timeout any changes of autofilled inputs
// https://github.com/angular/angular.js/issues/1460
// https://github.com/angular/angular.js/issues/1460#issuecomment-28662156
// Could break future Angular releases (if use `compile()` instead of `link())
// TODO support select
angular.module("app").config(["$provide", function($provide) {
var inputDecoration = ["$delegate", "inputsWatcher", function($delegate, inputsWatcher) {
var directive = $delegate[0];
var link = directive.link;
function linkDecoration(scope, element, attrs, ngModel){
var handler;
// By default model.$viewValue is equals to undefined
if(attrs.type == "checkbox"){
inputsWatcher.registerInput(handler = function(){
var value = element[0].checked;
// By default element is not checked
if (value && ngModel.$viewValue !== value) {
ngModel.$setViewValue(value);
}
});
}else if(attrs.type == "radio"){
inputsWatcher.registerInput(handler = function(){
var value = attrs.value;
// By default element is not checked
if (element[0].checked && ngModel.$viewValue !== value) {
ngModel.$setViewValue(value);
}
});
}else{
inputsWatcher.registerInput(handler = function(){
var value = element.val();
// By default value is an empty string
if ((ngModel.$viewValue !== undefined || value !== "") && ngModel.$viewValue !== value) {
ngModel.$setViewValue(value);
}
});
}
scope.$on("$destroy", function(){
inputsWatcher.unregisterInput(handler);
});
// Exec original `link()`
link.apply(this, [].slice.call(arguments, 0));
}
// Decorate `link()` don't work for `inputDirective` (why?)
/*
directive.link = linkDecoration;
*/
// So use `compile()` instead
directive.compile = function compile(element, attrs, transclude){
return linkDecoration;
};
delete directive.link;
return $delegate;
}];
$provide.decorator("inputDirective", inputDecoration);
$provide.decorator("textareaDirective", inputDecoration);
//TODO decorate selectDirective (see binding "change" for `Single()` and `Multiple()`)
}]).factory("inputsWatcher", ["$interval", "$rootScope", function($interval, $rootScope){
var INTERVAL_MS = 500;
var promise;
var handlers = [];
function execHandlers(){
for(var i = 0, l = handlers.length; i < l; i++){
handlers[i]();
}
}
return {
registerInput: function registerInput(handler){
if(handlers.push(handler) == 1){
promise = $interval(execHandlers, INTERVAL_MS);
}
},
unregisterInput: function unregisterInput(handler){
handlers.splice(handlers.indexOf(handler), 1);
if(handlers.length == 0){
$interval.cancel(promise);
}
}
}
}]);

From: Developer.mozilla.org docs Turning_off_form_autocompletion
If an author would like to prevent the auto-filling of password fields
in user management pages where a user can specify a new password for
someone other than themselves, autocomplete="new-password" should be
specified, though support for this has not been implemented in all
browsers yet.
So, what makes it work for me:
set autocomplete="new-password" on the password field
set autocomplete="off" in the username field.
I hope that it works for you too :)

As said here, https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form
The Google Chrome UI for auto-complete requests varies, depending on
whether autocomplete is set to off on input elements as well as their
form. Specifically, when a form has autocomplete set to off and its
input element's autocomplete field is not set, then if the user asks
for autofill suggestions for the input element, Chrome might display a
message saying "autocomplete has been disabled for this form." On the
other hand, if both the form and the input element have autocomplete
set to off, the browser will not display that message. For this
reason, you should set autocomplete to off for each input that has
custom auto-completion.
You need to set autocomplete="off" on both form and input
I don't think this is related to AngularJS

I had the same issue and found a very simple solution that just uses jQuery to grab the value on submit. In my controller I have the following:
$scope.username = "";
$scope.password = "";
$scope.login = function(){
$scope.username = $("#username").val();
$scope.password = $("#password").val();
// Proceed as normal
};
There are some downsides, if you need to do validation etc but otherwise it's fine for smaller forms like this.

You could watch the email field value and everytime the value in that field is changing, you could trigger a "change"-event on the password field. This events trigger all the ng-model magic on that field and updates the model.
module.directive("autocompleteFor", function () {
return {
restrict: "A",
link: function ($scope, $element, $attrs) {
$scope.$watch($attrs.autocompleteFor, function () {
$element.triggerHandler("change");
})
}
}
});
With this directive you could handle that scenario like this:
<input type="email" name="email" ng-model="user.email">
<input type="password" autocomplete-for="user.email" name="password" ng-model="user.password" required>
-----------------------------

To disable the autocomplete/autofill from a input, just type:
- autocomplete="false" instead of autocomplete="off"!

Below directive worked for me. It's simple and clean fix. Hope that helps!
Ref: AngularJS browser autofill workaround by using a directive
Here is a solution that is far less hacky than other solutions presented and is semantically sound AngularJS: VictorBlog.com
myApp.directive('formAutofillFix', function() {
return function(scope, elem, attrs) {
// Fixes Chrome bug: https://groups.google.com/forum/#!topic/angular/6NlucSskQjY
elem.prop('method', 'POST');
// Fix autofill issues where Angular doesn't know about auto-filled inputs
if(attrs.ngSubmit) {
setTimeout(function() {
elem.unbind('submit').submit(function(e) {
e.preventDefault();
elem.find('input, textarea, select').trigger('input').trigger('change').trigger('keydown');
scope.$apply(attrs.ngSubmit);
});
}, 0);
}
};
});
Then you simply attach the directive to your form:
<form ng-submit="submitLoginForm()" form-autofill-fix>
<div>
<input type="email" ng-model="email" ng-required />
<input type="password" ng-model="password" ng-required />
<button type="submit">Log In</button>
</div>
</form>

alternative solution is just to get rid off form element and use ng-form instead, it disables all browser interferings
<div ng-form="yourFormName" class="form-signin" ng-submit="login()">

Old question, but whatever
I came across the same problem and I've got a small "hack" solution. This problem happened at many different places in my app, so I created a directive for reusability.
module.directive("fakeAutocomplete", [
function () {
return {
restrict: "EA",
replace: true,
template: "<div><input/><input type=\"password\"/></div>",
link: function (scope, elem, attrs) {
elem.css({
"overflow": "hidden",
"width": "0px",
"height": "0px"
});
}
}
}
]);
And simply add
<fake-autocomplete></fake-autocomplete>
At the beginning of your form and the browser will detect the fake fields as the ones that should autocomplete. Simply putting display:none on the fields also does not seem to work anymore, I've tested it.

In my case, i set property autocomplete="off" in form and input.
<form autocomplete="off">
<input type="text" autocomplete="off">
</form>

It could be much simpler solution to the problem.
Angularjs couldn't "see" the value
Take the value via DOM (jQuery) then put it back into Angularjs.
```
angular.module('someModule').directive('chromeAutofillHack', function()
{
return {
require: '^ngModel',
restrict: 'A',
priority: 500, // set higher priority then other custom directives
link: function(scope, element, attrs , ngModelCtrl)
{
ngModelCtrl.$parsers.unshift(function(email)
{
if (!email) { // only do this when angular think there is no value
email = $(element).val();
ngModel.$setViewValue(email);
}
return email;
});
}
};
});
```

--- NO LONGER RELEVANT ---
I was able to disable autocomplete (weirdly enough) by adding the following.
<form ... novalidate>
<input ... formnovalidate />
Reference this Plunker

My solution for Chrome 35.0, Firefox 30.0, angular 1.2.18 (login page with password manager, autofill, angular method and redirect):
How does browser know when to prompt user to save password?

I ended up with a different solution that I don't see here yet. From what I found, the password value isn't exposed to the model (or possibly even the js api) until the user interacts with the page. Clicking the login button is enough interaction to make the value available, and the data binding will succeed early enough for the click handler on the button to access the password from the model. So if I could detect that the browser has auto-filled, I could enable the login button even though my model hadn't been updated yet. So I wrote a simple helper service to see if Chrome has auto-filled any inputs:
utilApp.service('autoFillDetectionService', [function () {
return {
hasAutoFillInputs: function () {
try{
return !!$(':-webkit-autofill').length;
}
catch (x) {
// IE gets here, it/jquery complains about an invalid pseudo-class
return false;
}
}
};
}]);
From the login controller, I have an interval checking if any input fields are marked as autofill and if so enable the login button.

Just Replace autocomplete="off" with autocomplete="new-password".

Related

Use $validators on blur/focus events

Now, in my validation directive I set validation state 'manually', like this:
$element.on('focus', function() {
$scope.$apply(function() {
ngModelCtrl.$setValidity('length', true);
});
});
$element.on('blur', function() {
$scope.$apply(function() {
if (ngModelCtrl.$modelValue && ngModelCtrl.$modelValue.length === +$attrs.maxlength) {
ngModelCtrl.$setValidity('length', true);
}
else if (ngModelCtrl.$modelValue.length < +$attrs.maxlength && ngModelCtrl.$modelValue.length > 0) {
ngModelCtrl.$setValidity('length', false);
}
}
});
But I want to set validation states with $validators with saving validation behaviour (on blur/focus events).
I can't use ng-model-options with updateOn: 'blur'.
Is other options to do this?
So to use a custom validator you will need to structure your directive a little differently. The major difference is you need to require ngModel.
Directive
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
// Run validator for maxLenth
ngModel.$validators.customMaxLength = function(value) {
var status = false;
if(value.length <= attrs.maxLengthValidator) {
console.log(attrs.maxLengthValidator);
status = true;
}
return status;
};
}
}
Something important to note. If you are using an attribute to do your validate against and the value can change you need to watch the value and run your validator function against in manually. Because the model does not change the validator would not fire automatically.
Add the following to the directive
scope.$watch(function() {
return attrs.maxLengthValidator;
},function() {
ngModel.$validate();
});
If you validator returns false you form will automatically have $valid set to false
HTML
In the example below I use angular-messages to display the valid output. This is an optional module that needs included.
<form name="myForm">
<div class="form-group" ng-class="{'has-error':myForm.testField.$invalid}">
<label class="control-label" for="testInput">Test Field</label>
<input class="form-control" type="text" name="testField"
max-length-validator="3" required ng-model="testField"/>
</div>
</form>
<div ng-messages ng-messages-multiple class="bg-danger" for="myForm.testField.$error">
<div ng-message when="required">Please enter value</div>
<div ng-message when="customMaxLength">To Long</div>
</div>
Custom Validator Example
In regards to your question about firing the validator on blur
You don't really want to update the ngModel if the model value is invalid.
What I would recommend is using styles and a focus attribute with the field to toggle the display based on the focus of the field.
Add something like the following to your input field
ng-focus="myForm.testField.focus=true" ng-blur="myForm.testField.focus=false"
Then use checks with ng-class, ng-show or ng-hide to update your display.
This has been added to the plnkr

How do I reset a form in angularjs?

See Fiddle: http://jsfiddle.net/hejado/7bqjqc2w/
I'm trying to form.reset() my form using angular.
HTML:
<div ng-controller="formCtrl">
<form name="resetme" id="resetme">
<input ng-model="text" type="text" />
<input file-model="file" type="file" />
<button type="button" ng-click="resetForm()">reset</button>
</form>
</div>
JS:
.controller('formCtrl', function($scope) {
$scope.resetForm = function() {
//$scope.resetme.reset();
document.getElementById('resetme').reset();
};
});
Please note: I'm using this kind of form to ajax-upload a file. The page is not refreshing and I don't want to use any reset-buttons. (I'm using one in the fiddle for simplicity.) I want to call the reset-function after the fileupload is finished (via http success).
I'm using
<input type="file" />
so I can't reassign empty values to all my inputs, because file inputs are readonly.
Calling the reset() function on the DOM element works, but I was told talking to the DOM in angular would be evil, so...
I'd like to know, how this would be done the angular way. I tried naming the form and referencing it via $scope.formname but I'm not able to call Web API functions... (commented line)
How can I achieve this?
UPDATE
After reading some of the answers, I should make clear, that I am using ngModel and a custom directive fileModel to get a hold of the file-object.
Some of the solutions worked in resetting the value of the input field, but the model is not cleared (neither file, nor text). Custom directives are the answer to that, but this kinda exceeds the scope of this question.
I wrote about this topic a couple years ago. I don't know if the Angular team has yet implemented a native form reset directive but you can do so yourself. There are a couple caveats to this implementation: it only works for one model (if you need to support more see the followup post) and the issue of when to initialize the original values. Also, I never tested this with file inputs so I am not sure it would work with those.
There was an issue for this but it was closed due to inactivity. :/
var myApp = angular.module('myApp', []);
myApp.controller('MyCtrl', ['$scope',
function($scope) {
$scope.myModel = {
foo: 'Boop',
bar: 'Beep'
};
$scope.myModelCopy = angular.copy($scope.myModel);
}
]);
myApp.directive('resetDirective', ['$parse',
function($parse) {
return function(scope, element, attr) {
var fn = $parse(attr.resetDirective);
var masterModel = angular.copy(fn(scope));
// Error check to see if expression returned a model
if (!fn.assign) {
throw Error('Expression is required to be a model: ' + attr.resetDirective);
}
element.bind('reset', function(event) {
scope.$apply(function() {
fn.assign(scope, angular.copy(masterModel));
scope.form.$setPristine();
});
// TODO: memoize prevention method
if (event.preventDefault) {
return event.preventDefault();
} else {
return false;
}
});
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<form reset-directive="myModel" name="form">
<input type="text" ng-model="myModel.foo" />
<input type="text" ng-model="myModel.bar" />
<input type="reset" value="Reset" />
<pre>myModel: {{ myModel | json }}</pre>
<pre>myModelCopy: {{ myModelCopy | json }}</pre>
<pre>form pristine: {{ form.$pristine }}</pre>
</form>
</div>
</body>
You can try :
reset
$scope.resetForm = function(form) {
//Even when you use form = {} it does not work
form.fieldA = null;
form.fieldB = null;
///more fields
}
Or
$scope.resetForm = function(form) {
//Even when you use form = {} it does not work
angular.copy({},form);
}
See Demo
You'd want to attach ng-model to each of your input fields then null them out via $scope. Either that or make a custom directive
I've just had a similar problem with forms not resetting. Here's what I would do:
In your resetform() function, I would include statements that set both of your ng-models in your input to "". For example:
**HTML**
<input ng-model="text" type="text" />
<input file-model="file" type="file" />
**JS**
.controller('formCtrl', function($scope) {
$scope.resetForm = function() {
$scope.text = "";
$scope.file = null;
};
});
Not certain if this will work for file-models but I'm certain it will remove the text. Best of luck!
If you don't want to use ng-model and proper reset type of button you can use this code, however this is not proper angular way to reset the form but it will work
$scope.reset = function(){
$('form').children('*').each(function(){
$(this).val('');
});
}
Here's the Plunker
To reset the form data use following code :
$scope.resetEmployeeData = function() {
$scope.employeeCred.userName = '';
$scope.employeeCred.employeeNo = '';
$scope.employeeCred.sapNo = '';
$scope.employeeCred.emailD = '';
$scope.employeeCred.mobileNo = '';
**this**.createEmployee.$setPristine();
**this**.createEmployee.$setUntouched();
};
use this rather than $scope.

How can I watch an ngModel value before it's valid?

I have a password field with a passwordStrengthMeter element directive beside it. I'd like the passwordStrengthMeter to observe changes to the password field and update in real time -- not just on blur or when the password field's model passes validation.
I'm using Angular validation on the password field, I'm finding that my directive cannot observe changes to the password field until the password field is valid. I want password validation to remain intact, but I also want realtime strength indication. How can I make this work? Here is what I have so far...
HTML:
<input class="control" type="password" name="password" placeholder="Password" ng-model="account.password"
ng-minlength="8" ng-maxlength="15" ng-pattern="VALIDATION_PATTERNS.passwordStrength" required ng-focused />
<password-strength-meter password-field="account.password"></password-strength-meter>
And the directive:
angular.module('app.directives').directive('passwordStrengthMeter', function() {
'use strict';
return {
restrict: 'E',
replace: true,
template: '<div class="password-strength-meter"></div>',
link: function(scope, element, attrs) {
// Map scores to CSS classes.
var scoreClasses = {
0: 'blank',
1: 'weak',
2: 'moderate',
3: 'strong',
4: 'very-strong'
};
var scorer = function() {
// Get password value.
var password = scope.$eval(attrs.passwordField);
// Calculate a score.
// TODO Replace this with an actual calculation.
return password.length;
}
scope.$watch(scorer, function(score) {
// Remove any score classes for the element.
for (var i in scoreClasses) {
element.removeClass(scoreClasses[i]);
}
// Set class based on score.
element.addClass(scoreClasses[score]);
});
}
};
});
It seems that setting
ng-model-options="{allowInvalid: true}"
on the password input field works around the issue.
If anyone has an alternative or better solution, feel free to post.

Form validation - Required one of many in a group

In the project I'm working on at the moment I currently have three textboxes and I need to validate that at least one of the text boxes has been populated.
I've been reading into custom validation with Angular directives and I understand you can set the validity of an input in a directive's link function using the following:
ctrl.$parsers.unshift(function(viewValue) {
// validation logic here
});
The problem I have is that I don't need to set an individual input's validity.. I need to invalidate the entire form if the criteria isn't met. I just wonder how to approach this?
I'm thinking maybe I should create a directive that's placed on the enclosing form itself and then make the form invalid?
I suppose I'm just looking for some guidance into how I should go about this because I'm a little unclear where to start - all the material I'm reading on custom validation seems to be for when you're validating a specific input as opposed to a set of conditions on a form.
I hope I've made myself clear! Thanks..
You can use ng-required to force the user to fill at least one field by checkingthe length attribute of the string.
You can do the following for example:
<form name="myForm">
<input type="text" ng-model="fields.one" name="firstField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" />
<br/>
<input type="text" name="secondField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" ng-model="fields.two" />
<br/>
<input type="text" ng-model="fields.three" name="thirdField" ng-required="!(fields.one.length || fields.two.length || fields.three.length)" />
<br/>
<button type="submit" ng-disabled="!myForm.$valid">Submit</button>
</form>
See this working fiddle example for more details.
You can have more details about required vs ng-required by reading this question
There are several approaches and the best option depends on your exact requirements.
Here is one approach that I found to be generic enough and flexible.
By "generic" I mean it doesn't only work for text-fields, but also for other kinds of inputs, such as check-boxes.
It's "flexible" because it allows any number of control-groups, such that at least one control of each group must be non-empty. Additionally, there is no "spacial" constraint - the controls of each group can be anywhere inside the DOM (if required, it is easy to constrain them inside a single form).
The approach is based on defining a custom directive (requiredAny), similar to ngRequired, but taking into account the other controls in the same group. Once defined, the directive can be used like this:
<form name="myForm" ...>
<input name="inp1" ng-model="..." required-any="group1" />
<input name="inp2" ng-model="..." required-any="group1" />
<input name="inp3" ng-model="..." required-any="group1" />
<input name="inp4" ng-model="..." required-any="group2" />
<input name="inp5" ng-model="..." required-any="group2" />
</form>
In the above example, at least one of [inp1, inp2, inp3] must be non-empty, because they belong to group1.
The same holds for [inp4, inp5], which belong to group2.
The directive looks like this:
app.directive('requiredAny', function () {
// Map for holding the state of each group.
var groups = {};
// Helper function: Determines if at least one control
// in the group is non-empty.
function determineIfRequired(groupName) {
var group = groups[groupName];
if (!group) return false;
var keys = Object.keys(group);
return keys.every(function (key) {
return (key === 'isRequired') || !group[key];
});
}
return {
restrict: 'A',
require: '?ngModel',
scope: {}, // An isolate scope is used for easier/cleaner
// $watching and cleanup (on destruction).
link: function postLink(scope, elem, attrs, modelCtrl) {
// If there is no `ngModel` or no groupName has been specified,
// then there is nothing we can do.
if (!modelCtrl || !attrs.requiredAny) return;
// Get a hold on the group's state object.
// (If it doesn't exist, initialize it first.)
var groupName = attrs.requiredAny;
if (groups[groupName] === undefined) {
groups[groupName] = {isRequired: true};
}
var group = scope.group = groups[groupName];
// Clean up when the element is removed.
scope.$on('$destroy', function () {
delete(group[scope.$id]);
if (Object.keys(group).length <= 1) {
delete(groups[groupName]);
}
});
// Update the validity state for the 'required' error-key
// based on the group's status.
function updateValidity() {
if (group.isRequired) {
modelCtrl.$setValidity('required', false);
} else {
modelCtrl.$setValidity('required', true);
}
}
// Update the group's state and this control's validity.
function validate(value) {
group[scope.$id] = !modelCtrl.$isEmpty(value);
group.isRequired = determineIfRequired(groupName);
updateValidity();
return group.isRequired ? undefined : value;
}
// Make sure re-validation takes place whenever:
// either the control's value changes
// or the group's `isRequired` property changes
modelCtrl.$formatters.push(validate);
modelCtrl.$parsers.unshift(validate);
scope.$watch('group.isRequired', updateValidity);
}
};
});
This might not be so short, but once included into a module, it is very easy to integrate into your forms.
See, also, this (not so) short demo.
It's too late but might be can save some one's time:
If there are only two fields, and want to make one of them required then
<input type="text"
ng-model="fields.one"
ng-required="!fields.two" />
<br/>
<input type="text"
ng-model="fields.two"
ng-required="!fields.one" />
If you have three like in question then
<input type="text"
ng-model="fields.one"
ng-required="!(fields.two || fields.three)" />
<br/>
<input type="text"
ng-model="fields.two"
ng-required="!(fields.one || fields.three)" />
<br/>
<input type="text"
ng-model="fields.three"
ng-required="!(fields.one|| fields.two)" />
If more than this, I will suggest to write a function on scope and watch it.
See the working example
modification to ExpertSystem's answer (https://stackoverflow.com/a/24230876/4968547) so that his code works in the latest angularjs.
i changed the updateValidity() to set parse also to true/false
function updateValidity() {
if (group.isRequired) {
modelCtrl.$setValidity('required', false);
modelCtrl.$setValidity('parse', false);
} else {
modelCtrl.$setValidity('required', true);
modelCtrl.$setValidity('parse', true);
}
}
now its working fine for me
Ran into this same problem last week; ExpertSystem's solution was a good start, but I was looking for a few enhancements to it:
Use Angular 1.4.3
Use ngMessages
I eventually wound up with this example on JSFiddle - hope that helps inspire others in the same boat! Relevant JS code from the Fiddle:
var app = angular.module('myApp', ['ngMessages']);
app.controller('myCtrl', function ($scope) {
$scope.sendMessage = function () {
$scope.myForm.$submitted = true;
if ($scope.myForm.$valid) {
alert('Message sent !');
}
};
});
app.directive('requiredAny', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function postLink(scope, elem, attrs, ctrl) {
// If there is no 'ngModel' or no groupName has been specified,
// then there is nothing we can do
if (!ctrl || !attrs.requiredAny) { return };
// If this is the first time we've used this directive in this scope,
// create a section for it's data. If you need / want to make use of
// an isolate scope you'll need to make 'var groups' scoped to the directive;
// but then you may want to look in to clearing out group entries yourself
if (!scope.__requiredAnyGroups) {
scope.__requiredAnyGroups = {}
}
var groups = scope.__requiredAnyGroups;
// Create a bucket for this group if one does not yet exist
if (!groups[attrs.requiredAny]) {
groups[attrs.requiredAny] = {};
}
var group = groups[attrs.requiredAny];
// Create the entry for this control
group[attrs.ngModel] = {
ctrl: ctrl,
hasValue: false
};
ctrl.$validators.requiredAny = function(view, value) {
var thisCtrl = group[attrs.ngModel],
ctrlValue = (typeof value !== 'undefined') && value,
oneHasValue = false;
thisCtrl.hasValue = ctrlValue;
// First determine if any field in the group has a value
for (var prop in group) {
if (group.hasOwnProperty(prop) && group[prop].hasValue) {
oneHasValue = true;
break;
}
}
// Set the validity of all other fields based on whether the group has a value
for (var prop in group) {
if (group.hasOwnProperty(prop) && thisCtrl != group[prop]) {
group[prop].ctrl.$setValidity('requiredAny', oneHasValue);
}
}
// Return the validity of this field
return oneHasValue;
};
}
};
});
Here is a refactored take on ExpertSystems great post. I didn't need the destroy method so I gutted it.
I also added a grayed out explanation that may help in your code. I use this directive for ALL my required fields. Meaning when I use this directive I no longer use ng-required, or required.
If you want a field required just pass in a unique group name. If you don't want the field required then pass in null, and if you want to have many different groups just pass in a matching group name.
I believe there is a little more refactoring that could be done here. Angularjs states that when using $setValidity, that instead you should use $validators pipeline instead, but I could not get that to work. I am still learning this complex animal. If you have more info, post it!
app.directive('rsPartiallyRequired', function () {
var allinputGroups = {};
return {
restrict: 'A',
require: '?ngModel',
scope: { },
link: function(scope, elem, attrs, ctrl) {
if( !ctrl || !attrs.rsPartiallyRequired ){ return } // no ngModel, or rsPartialRequired is null? then return.
// Initilaize the following on load
ctrl.$formatters.push( validateInputGroup ); // From model to view.
ctrl.$parsers.unshift( validateInputGroup ); // From view to model.
if ( ! allinputGroups.hasOwnProperty( attrs.rsPartiallyRequired )){ // Create key only once and do not overwrite it.
allinputGroups[ attrs.rsPartiallyRequired ] = { isRequired: true } // Set new group name value to { isRequired: true }.
}
scope.inputGroup = allinputGroups[ attrs.rsPartiallyRequired ] // Pass { isRequired: true } to form scope.
function validateInputGroup(value) {
scope.inputGroup[ scope.$id ] = !ctrl.$isEmpty( value ); // Add to inputGroup ex: { isRequired: true, 01E: false }.
scope.inputGroup.isRequired = setRequired( attrs.rsPartiallyRequired ); // Set to true or false.
updateValidity(); // Update all needed inputs based on new user input.
return scope.inputGroup.isRequired ? undefined : value
}
function setRequired(groupName) {
if( ! allinputGroups[ groupName ] ){ return false } // No group name then set required to false.
return Object.keys( allinputGroups[ groupName ] ).every( function( key ) { // Key is 'isRequired' or input identifier.
return ( key === 'isRequired' ) || ! allinputGroups[ groupName ][ key ]
});
}
scope.$watch('scope.inputGroup.isRequired', updateValidity); // Watch changes to inputGroup and update as needed.
function updateValidity() { // Update input state validity when called.
ctrl.$setValidity('required', scope.inputGroup.isRequired ? false : true );
}
}
}
});
// This directive sets input required fields for groups or individual inputs. If an object in the template is given
// to the directive like this:
// Object: { "name": "account_number", "attrs": { "required": { "group": "two" }}}.
// HTML: <input type="text" rs-partially-required="{{ field.attrs.required.group }}" />
// Or anything where the evaluation is a string, for example we could use "groupOne" like this...
// HTML: <input type="text" rs-partially-required="groupOne" />
// Then this directive will set that group to required, even if it's the only member of group.
// If you don't want the field to be required, simply give the directive a null value, like this...
// HTML: <input type="text" rs-partially-required="null" />
// However, when you want to use this directive say in an ngRepeat, then just give it a dynamic string for each input
// and link the inputs together by giving the exact matching string to each group that needs at least one field. ex:
// <input type="text" rs-partially-required="null" />
// <input type="text" rs-partially-required="one" />
// <input type="text" rs-partially-required="two" />
// <input type="text" rs-partially-required="one" />
// <input type="text" rs-partially-required="null" />
// <input type="text" rs-partially-required="three" />
// <input type="text" rs-partially-required="three" />
// <input type="text" rs-partially-required="three" />
// In the above example, the first and fifth input are not required and can be submitted blank.
// The input with group "two" is the only one in the group, so just that input will be required by itself.
// The 2 inputs with "one" will be grouped together and one or the other will require an input before
// the form is valid. The same will be applied with group "three".
// For this form to be valid, group "two" will be required, and 1 input from group one will be required,
// and 1 input from group three will be required before this form can be valid.
You can add required attribute for each of them , and at the end , you can rely your validation on each/all/or just one of them
<form name="form" novalidate ng-submit="submit()">
// novalidate is form disabling your browser's own validation mechanism
<input type="text" required ng-model="texts.text1">
<input type="text" required ng-model="texts.text2">
<input type="text" required ng-model="texts.text3">
// you can do validation in variety of ways , but one of them is to disable your submit button until one of the textboxes are filled correctly like this :
<button type="submit" ng-disabled="form.text1.$invalid && form.text2.$invalid && form.text3.$invalid"></button>
</form>
This way if just one of them is filled , button will be enable
I don't know how you're gonna show that form is not valid , but I think desabling the submit button is the general way
I had similar grouping requirement in my project and I wrote this.Interested people can use this
.directive('group',function(){
return {
require: '^form',
link : function($scope,element,attrs,formCtrl){
var ctrls =[];
element.find(".group-member").each(function(){
var member = angular.element($(this));
var mdlCtrl = member.data("$ngModelController");
if(!mdlCtrl){
throw "Group member should have ng-model";
}
ctrls.push(mdlCtrl);
});
var errKey = attrs['name']+"GrpReqd";
var min = attrs['minRequired'] || 1;
var max = attrs['maxRequired'] || ctrls.length;
$scope.validateGroup = function(){
var defined=0;
for(i=0;i<ctrls.length;i++){
if(ctrls[i].$modelValue){
defined++;
}
}
if(defined < min || defined > max){
formCtrl.$setValidity(errKey,false);
} else {
formCtrl.$setValidity(errKey,true);
}
};
//support real time validation
angular.forEach(ctrls,function(mdlCtrl){
$scope.$watch(function () {
return mdlCtrl.$modelValue;
}, $scope.validateGroup);
});
}
};
})
HTML usage :
<div name="CancellationInfo" group min-required="1" max-required="1">
<input type="text" class="form-control group-member" style="width:100%;" name="Field1" ng-model="data.myField" />
<input type="text" class="form-control group-member" style="width:100%;" name="Field1" ng-model="data.myField2" />
<input type="text" class="form-control group-member" style="width:100%;" name="Field2" ng-model="data.myField3" />
</div>
Here group directive identifies the logical grouping. This directive sits on an element without ng-model, a div in the above example. group directive receives 2 optional attribute min-required and max-required. Group members are identified using group-member class on individual fields. Group members are supposed to have an ng-model for binding. Since group directive doesn't have an ng-model error will be emitted under yourForm.$error.CancellationInfoGrpReqd in the above case. Unique Error key is generated from the element name on which group directive is sitting with GrpReqd appended to it.

AngularJS how to submit untouched, empty form fields?

I've got a problem with a form. I'm submitting the form and some of the fields don't have to be filled, when the data is send to a restful API I see that angular doesn't pick up the empty, never touched, never changed, input fields.
But on the server side the field is expected to be present. How can I get angular to send these fields as well?
I have two inputs (data.Record.one, data.Record.two), when I fill one and submit the form I'm ending up with this:
{
"Record": {
"one": "test"
}
}
I guess this is because angular doesn't "see" untouched and empty fields.
But I want that:
{
"Record": {
"one": "test",
"two": ""
}
}
Here is a demo of what I try: http://plnkr.co/edit/Ky0PQ9U5o1V7IyU9QgkK
I'm well aware that I could initialize the form with an empty skeleton object but I would really prefer to not have to do that. If I need to do that it's just additional code that as a bonus will just introduce an area of potential fail if another dev adds a field in the form but forgets / doesn't know about the skeleton in the JS code.
Angular treats empty fields as empty/null by default. You can disable this by adding ng-trim="false" on your input fields.
This will not add extra code, only definition on your markup.
https://docs.angularjs.org/api/ng/input/input%5Btext%5D
EDIT: I see now what you want. Angular takes good care of your model (data). Just because you bind an input field to a sub,sub property won't initialize the property with empty string. The input fields does not contain empty string - they contain null which is what you put in them in the first place (by binding them to an uninitalized property).
The feature you want, is for angular to initialize your data.Record with the properties you need. You could make a custom directive for that called initialize-property that would set its model to an empty string.
EDIT: I created the directive but plunker won't let me access due to some cross domain problems. You should be able to use it in your own work though
myApp.directive('initializeProperty', function() {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.val('');
}
};
});
And then on your input fields use this:
One <input name="one" ng-model="data.Record.one" initialize-property ng-trim="false" /><br />
Two <input name="one" ng-model="data.Record.two" initialize-property ng-trim="false" /><br />
replace your app.js code to this one
var myApp = angular.module('myApp', []);
myApp.controller('TestFormController', ['$scope', '$log', function($scope, $log) {
$scope.data = {
Record : {
"one": "",
"two": ""
}
}
$scope.add = function() {
$log.log($scope.data);
}
}]);
DEMO
UPDATE !
change your view with this html
One <input name="one" ng-init="data.Record.one = ''" ng-model="data.Record.one" /><br />
Two <input name="one" ng-init="data.Record.two = ''" ng-model="data.Record.two" /><br />
i have added extra ng-init directive which will initilize your data
DEMO
I fixed it simply using ng-init="inputfieldname=''"
I had the same problem, so then in my research I find this:
http://blog.fernandomantoan.com/angularjs-forcando-bind-de-todos-os-campos-no-submit/
Solve my problem, but I had to edit, thus:
.directive('forceBind', function() {
return {
require: '^form',
priority: -1,
link: function (scope, element, attrs, form) {
element.bind('submit', function() {
angular.forEach(form, function(value, key) {
if(typeof value == 'object'){
if (value.hasOwnProperty('$modelValue')) {
if (!value.$viewValue) {
value.$setViewValue("");
}
}
}
});
});
}
};
});
So, when the event submit runs, before entering in method the directive force bind.
I recommend using ng-submit for validation.
Sorry my english.

Categories

Resources