Related
I'm building an angular directive, where I have two number inputs and both together represent an age range.
What I want to achieve is being able to use this directive the following way:
<input-age-range
name="ageRange"
ng-model="filterUpdateVM.ageRange">
</input-age-range>
<span ng-if="myCtrlVM.form.ageRange.$error.ageRange">
Please, enter a valid age range.
</span>
and be able to show a custom error when the entered age range is not correct.
My directive html template looks this way:
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Minimum age"
name="ageMin"
min="18"
max="80"
ng-change="checkAndValidateAgeRange()"
ng-model="ageRange.ageMin"
ng-model-options="{allowInvalid: true}">
</div>
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Maximum age"
name="ageMax"
min="18"
max="80"
ng-change="checkAndValidateAgeRange()"
ng-model="ageRange.ageMax"
ng-model-options="{allowInvalid: true}">
</div>
Every time the user types anything in an input, I wish to check the if the entered range is correct:
Both ages should be numbers
Both ages should be between 18 and 80
ageMin <= ageMax
So far ng-model-options="{allowInvalid: true}" will let the ngChange function inside my directive to be triggered in case any of the ages entered is not between the desired age range (18-80) - this way I can do some checking and set an input error if there's any and show it.
My problem here is that if the age entered at first is not a number, ngChange is not called: it won't be able to do the error checking so there won't be any errors to be shown. How can I have my ngChange function called in this case, without changing my input type="number"?
EDIT: Jsfiddle added: http://jsfiddle.net/afkf96qh/
Finally after a day of trying to figure out what was going on, I achieved the behavior I was looking for.
First of all, ng-model-options="{allowInvalid: true}" had nothing to do in solving my problem, so I removed that out of my input. My directive html template looks this way now:
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Minimum age"
name="ageMin"
min="18"
max="80"
ng-model="ageRange.ageMin"
ng-change="checkAndValidateAgeRange()">
</div>
<div class="col-md-3">
<input type="number"
class="form-control"
placeholder="Maximum age"
name="ageMax"
min="18"
max="80"
ng-model="ageRange.ageMax"
ng-change="checkAndValidateAgeRange()">
</div>
The problem that caused not triggering ng-change="checkAndValidateAgeRange()" was the way I was initializing the ageRange variable at the ngModel $render function:
angular
.module('myModule')
.directive('myDirective', myDirective);
function myDirective() {
var directive = {
templateUrl: 'myTemplate.html',
restrict: 'E',
require: ['^form', 'ngModel'],
link: linkFunc
};
return directive;
/////////
function linkFunc(scope, element, attrs, ctrls) {
scope.form = ctrls[0];
scope.ageRange = {};
scope.checkAndValidateAgeRange = checkAndValidateAgeRange;
var ngModel = ctrls[1];
ngModel.$render = getAgeRange;
function checkAndValidateAgeRange() {
// fired when the model changes
};
// $render function
function getAgeRange() {
scope.ageRange.ageMin = ngModel.$viewValue.ageMin;
scope.ageRange.ageMax = ngModel.$viewValue.ageMax;
};
};
};
In some cases, both of the ngModel object's properties passed to the directive - ageMin and ageMax - could be undefined.
And we all know what happens when we have an input with restrictions such as type="[whatever]", min="18" or max="80" and the data entered in that input does not follow those requirements: it is set to undefined in the model.
So, if the above properties were initially passed to the directive set to undefined and you entered a value in the <input type="number ...> that, for example, was not numeric... the binded model's value would still be undefined, so there wouldn't be any changes in the model making the ng-change not trigger.
What I finally did to solve this, was to initialize both ageRange.ageMin and ageRange.ageMax to null if they are passed as undefined, at my $render function:
ngModel.$render = getAgeRange;
// $render function
function getAgeRange() {
scope.ageRange.ageMin = ngModel.$viewValue.ageMin || null;
scope.ageRange.ageMax = ngModel.$viewValue.ageMax || null;
};
This way, when entering an invalid input the model's property value will change from null to undefined causing the ng-change to trigger.
checkout this fiddle
$scope.checkAndValidateAgeRange = function(name, model) {
if (name === 'ageMin') {
if (angular.isNumber(model)) {
if (model < $scope.age.min) {
$scope.minAgeMessage = 'You are under age';
} else if (model > $scope.age.max) {
$scope.minAgeMessage = 'You are too old';
} else {
$scope.minAgeMessage = '';
}
} else {
$scope.minAgeMessage = 'Enter Only Numbers';
$scope.ageRange.ageMin = null;
}
}
if (name === 'ageMax') {
if (angular.isNumber(model)) {
if (model <= $scope.ageRange.ageMin) {
$scope.maxAgeMessage = 'You are under age';
} else if (model > $scope.age.max) {
$scope.maxAgeMessage = 'You are too old';
} else {
$scope.maxAgeMessage = '';
}
} else {
$scope.maxAgeMessage = 'Enter Only Numbers';
$scope.ageRange.ageMax = null;
}
}
}
you can't use allowInvalid on type='number' as given here. You can set type='text' and then handle validation inside the ng-change handler if your intent if using type='number' is showing validation messages.
To set validity of form inside a controller refer to this link.
number format. i want an input two decimal places on ng-blur without directive
i tried with directive which is working fine.
define(['angular', 'mainModule'], function(angular, mainModule) {
'use strict';
mainModule.directive('numbformat', ['$filter', function ($filter) {
return {
require: '?ngModel',
link: function (scope, elem, attrs, controller) {
if (!controller) return;
var DECIMAL_COUNT = 2;
if(attrs.decimal){//if require to change decimal value specify declimal value as attribute in HTML
DECIMAL_COUNT = attrs.decimal;
}
controller.$formatters.unshift(function (a) {
return $filter(attrs.numbformat)(controller.$modelValue, DECIMAL_COUNT);
});
//For on Blur of Element manage Decimal and Format issuse
elem.on('blur' , function() {
elem.val($filter(attrs.numbformat)(controller.$modelValue, DECIMAL_COUNT));
});
//For on Focus of Element manage Decimal and Format issuse
elem.on('focus' , function(){
var actVal = $filter(attrs.numbformat)(controller.$modelValue)
elem.val(actVal);
elem[0].select();
});
controller.$parsers.unshift(function (viewValue) {
var plainNumber = viewValue.replace(/[^\d|\-+|\.+]/g, '');
var actVal = plainNumber.toString();
if(actVal.indexOf('.') > -1 || actVal.indexOf('-') > -1){
return plainNumber;
}else{
elem.val($filter(attrs.numbformat)(plainNumber));
}
return plainNumber;
});
}
};
}]);
})
but it is giving me performance issues on IE11.
because directive is called on every input which is wrong so m trying it without directive and converting to it
ng-keyup="formatval($event,'ExpStatement.MinVal')"
ng-blur="formatValBlur($event,'ExpStatement.MinVal')"
now ng-keyup will allow only decimals or number
and on blur i want to decimal compulsory
in short i want to converting the directive. or any one help me to convert or any feasible solution to this
i tried this one but not working
$scope.formatval = function(event , val){
$scope.val = $filter('number')( parseFloat($scope.val.replace(/[^\d|\-+|\.+]/g, "")) );
}
Only allow a number in the textbox with (see here for more info including showing error if not a number https://docs.angularjs.org/api/ng/input/input%5Bnumber%5D)
<input type="number" name="number" placeholder="number only" required>
In your angular blur event/keyup function, use regular javascript to format the number.
$scope.val.toFixed(2);
Maybe this post can help you :
http://jsfiddle.net/gsferreira/Lsv9f0b0/
It use native api :
<input type="number" class="form-control" name="myDecimal" placeholder="Decimal" ng-model="myDecimal" ng-pattern="/^[0-9]+(\.[0-9]{1,2})?$/" step="0.01" required />
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
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.
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".