When using the password manager in Chrome to automatically fill in username & password on two md-input-containers, the password label will not float until _some_ action (e.g. clicking anywhere on the page) is done:
After initial page load:
After clicking anywhere:
Reproducing this isn't very easy, since services like Plnkr or CodePen either won't trigger the password manager or the auto-fill; however this GitHub page / repository should provide an environment where it should be reproducible:
Site: http://sebih.github.io/angular-material-pwmanagerbug/
Repo: https://github.com/SebiH/angular-material-pwmanagerbug
As there isn't any password authentification, simply type in something to (hopefully) trigger Chrome's password manager, save password and use the link to go back to the login page.
One interesting thing to note is that refreshing the login page does not seem to recreate the faulty state; however navigation via links breaks the password field again.
It seems to work properly in FireFox 35. I'm using Chrome 40, but noticed it in previous versions, too.
Pretty sure this is due to a bug in Chrome where password autofill doesn't trigger a change on inputs. Not sure there's much we can do about it until it gets resolved.
I know this is closed, but this has been a Chrome bug for some time and it may not be resolved for a while for all I know. So, I think, in the spirit of finding some solution as a workaround, this might be useful to people:
document.querySelector("input[type=password]").value = " ";
document.querySelector("input[type=password]").value = "";
document.querySelector("input[type=password]").blur()
This clears an autocomplete value, so there is some drawback to this approach. However, it looks nicer. You might also be able to use autocomplete="off"
, but I haven't been able to solve this issue that way.
Should be fixed in #master with SHA a64291b3542be31abe5e79b098fef9b35aed375a
Problem seems to persist on Chrome (v43). I've updated the test repo/site since it returned 404, but should be up now again. Will test the workaround later.
I don't think we are out of the woods just yet, even given a64291b. I tried a few things to determine this. I:
bower install --save angular-material
. This did not fix the problem.bower install --save angular-material#master
. This did not fix the problem.bower install --save angular-material#a64291b
. This did not fix the problem.git clone https://github.com/angular/material.git; cd material; npm install; gulp build
and then I copied the build over to the project where I am using angular-material and rebuilt my project. This did not fix the problem.@Splaktar - can you investigate this?
Another point: the bug appears to have an intermittent nature. I can refresh @SebiH's repo/site and sometimes see the issue and sometimes not see the issue.
Here is my screenshot:
@morrissinger Seems to be related to keyboard events, possibly? On my machine, when refreshing via navigation or clicking the reload button with the mouse, the password label will not position itself correctly. When refreshing with F5, it works as expected.
This is because of this weird Chrome behavior:
The value
of autofilled password field is not set until user do any kind of interaction with page, including clicking somewhere or touching almost anything in keyboard!
This is described in more detail here.
I don't know it's because of security or something but it becomes bolder in angular-material, because of the floating label, however it exists even in big websites like Dropbox
I tried a various kinds of workarounds including simulating mouse or keyboard interaction, but none of them worked! It seems to be such an intractable bug!
One possible amelioration is to use "md-no-float", but doing that, not only you will lose the awesomeness of floating label, but also if you want to use form invalidity state to enable or disable submit button, problem remains!
As @SebiH stated in his last comment, this does have some interaction with exactly how the page is refreshed.
Just pressing any key after reloading the page w/ a problem causes the input to float the text properly.
Based on the two Chrome bugs referenced in this issue plus an additional duplicate Chrome bug (https://code.google.com/p/chromium/issues/detail?id=398805), this appears to be Working as Intended in Chrome.
There are hacks available. TBosch came up with https://github.com/tbosch/autofill-event just for this kind of issue. Unfortunately, I was not able to get that to work for me with the latest Angular about 9 months ago.
I was able to solve this in one project for Firefox by using a $timeout
, details here: http://stackoverflow.com/a/23919364/633107
But this hack does not appear to work for Chrome due to the above mentioned bugs related to Chrome AutoFill. The ngModel is never updated with the value from AutoFill until the user interacts with the page in some way.
Also as for the reason that the Placeholder text does not behave properly, this is related to the fact that md-input
uses a <label>
instead of a placeholder
. Chrome doesn't handle labels like a placeholders as per the spec.
Same issue with using md-label
's placeholder
attribute: http://splaktar.github.io/angular-material-pwmanagerbug/
It doesn't actually use the browser's placeholder feature. It just adds it as a label with float feature enabled via onClick
handler.
@alirezamirian suggested using md-no-float
for type="password"
inputs. This might be the best option available at this time.
md-no-float doesn't appear to fix the label issue with password fields. Using Angular Materials v0.10.0
After click on any Element, also the body - the password field regonized a change, and the input moved to top. I have tried to trigger the click event, unfortunately without success.
$timeout(function(){
angular.element(document).find('body').triggerHandler('click');
console.log('click but no changes');
}, 150);
Does anyone have any idea why this is a failure?
@webdesignberlin Yes, Chrome browser is purposely keeping the value from being given to the element until the user interacts with the page. This is done for security reasons. They only make it look like the value has been entered, but they don't actually pass the value to the element until interaction happens. They have patched up all of the hacks to get around this (like your example) that I have been able to find. I tried at least 10 different approaches without any luck.
@Splaktar That's not so great. Thanks for the information. Safety first. The only pity is that we live in a world where we have to pay attention to such things.
@sjlynch you should use it as class on label.
God I wish there was a way to disable that stupid autofill completely. Along with it's annoying yellow background.
Status: need more discussion about how to address this
@alirezamirian Thanks, that fixed it and it's a pretty acceptable solution.
Setting focus to the password field seems to work as well (with some 1s delay):
angular.element('#password').focus();
Edit: actually it seems to help sometimes, sometimes with delay, sometimes not at all. Might work with tuning $timeouts or something but meh...
same problem for me :(
+1
+1
This seems resolved for me as of 0.11.1.
Well this seems to be a little better in 0.11.2. At least when using placeholder
, you will not get the overlaid password/label issue. But you do get an issue with the input applying an md-icon-float
to the md-input-container
.
This results in the label getting a margin-left: 36px
applied to it, which leaves it too far to the right as seen in this image.
There is also an issue with the container expanding up a second line to contain the floating label (seen above, compared to a single yellow line below).
Using an input with a label
instead of a placeholder
does not work still as seen here:
@fxck the yellow background can be changed w/ an exaggerated box-shadow -
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px white inset;
}
OK, I've updated http://splaktar.github.io/angular-material-pwmanagerbug/ with the best way to work around this bug with the recent master and 1.0.0-rc1 build.
Here are the key parts:
<md-input-container md-no-float>
<input ng-model="user.password" type="password" name="password" required
placeholder="Password">
</md-input-container>
Note that md-no-float
is an attribute on the md-input-container
now and that we're using placeholder
instead of <label>Password</label>
. For consistency in the example code, I've set the Username field to use the same md-no-float/placeholder configuration.
If you still want a label and don't want a placeholder, then add the label outside of the md-input-container
and style it as desired.
Due to the security limitation of Chrome, there is no way to use floating labels with auto filled fields properly.
I think that this can be closed, but Thomas wanted to look at this a little bit more.
How about using a timeout to detect -webkit-autofill and add md-input-has-value to md-input-container?
$timeout(function () {
var elem = angular.element($document[0].querySelector('input[type=password]:-webkit-autofill'));
if (elem.length) {
elem.parent().addClass('md-input-has-value');
...
}
}, 150);
Thumbs up @antoinebrault! Your workaround works for me, in contrast to many other solutions posted on e.g. stackoverflow.
Side notes:
angular.element(selector)
with angular.element($document[0].querySelector(selector))
such that jQuery is not needed.$setTouched()
on the password property of my form.passwordAutoFilled
to true
. In the corresponding HTML I can then use <input .... ng-required="!passwordAutoFilled" />
.+1
@antoinebrault +1
I've spent a ridiculous amount of time fiddling around and researching this, thank you!
I made a decorator if anyone is interested.
@BlaiseGratton @leshin @Frank3K @ThomasBurleson
$provide.decorator('mdInputContainerDirective', function($delegate, $interval) {
var directive = $delegate[0];
directive.compile = function() {
return {
post: function($scope, element, attr, ctrl) {
var interval;
var count = 0;
if (ctrl.input[0].type === 'password') {
interval = $interval(function() {
if (count > 10) {
$interval.cancel(interval);
}
if (ctrl.input.parent()[0].querySelector('input:-webkit-autofill')) {
ctrl.element.addClass('md-input-has-value');
$interval.cancel(interval);
}
count++;
}, 25);
}
}
};
};
return $delegate;
});
*edit: $interval is used because delay on -webkit-autofill seems to be random.
+1
For those wondering why Material Theming is broken after applying this workaround, here is the fix. Simply requires adding:
$mdTheming(element);
Complete example:
var mdInputContainerDirective = ['$delegate', '$interval', '$mdTheming', function($delegate, $interval, $mdTheming) {
var directive = $delegate[0];
directive.compile = function() {
return {
post: function($scope, element, attr, ctrl) {
$mdTheming(element);
var interval;
var count = 0;
if (ctrl.input[0].type === 'password') {
interval = $interval(function() {
if (count > 10) {
$interval.cancel(interval);
}
if (ctrl.input.parent()[0].querySelector('input:-webkit-autofill')) {
ctrl.element.addClass('md-input-has-value');
$interval.cancel(interval);
}
count++;
}, 25);
}
}
};
};
return $delegate;
}];
Thank you @antoinebrault!
Sry, but what file added it?
+1 for the decorator. For newbies like me who have hard time with service/providers, the $provide is available in the angular.config() :
angular.module('myModule', ['ngMaterial'])
.config(function ($provide) {
$provide.decorator('mdInputContainerDirective', function ($delegate, $interval) {
var directive = $delegate[0];
....
});
Thanx, it works great :)
Thanks for that fix @davidmroth, I'm new to the angular decorator mechanism, but I guess there is a missing "return" in your code right ?
@patriceo he's missing "return $delegate;". Look at my code.
Yes thanks @antoinebrault !
How embarrassing. Copy/paste gone wrong. Fixed now.
Thanks @antoinebrault, @antoinebrault, and @patriceo!
For some reason I get null pointer exceptions on ctrl.input[0]
using the latest decorator code. I have changed the code as follows:
$provide.decorator('mdInputContainerDirective', function($mdTheming, $delegate, $interval) {
var directive = $delegate[0];
directive.compile = function() {
return {
post: function($scope, element, attr, ctrl) {
$mdTheming(element);
if (ctrl.input && ctrl.input.length && ctrl.input[0].type === 'password') {
var count = 0;
var interval = $interval(function() {
if (count > 10) {
$interval.cancel(interval);
}
if (ctrl.input.parent()[0].querySelector('input:-webkit-autofill')) {
ctrl.element.addClass('md-input-has-value');
$interval.cancel(interval);
}
count++;
}, 25);
}
}
};
};
return $delegate;
});
Note that I have also moved the interval
and counter
variables further inside.
Hi guys!
Question for @Splaktar or @ThomasBurleson
The solution proposed by @antoinebrault will review and laid out in any version?
Hello folks,
Was struggeling with this issue a while ago, and tried to solve it with webkit css like mentioned here.
But then I accidently tried switching label and input:
<md-input-container>
<input type="password" ng-model="password" name="password" required>
<label>Password</label>
</md-input-container>
Instead of
<md-input-container>
<label>Password</label>
<input type="password" ng-model="password" name="password" required>
</md-input-container>
Don't know if this works for you guys, but it worked out for me!
You're right. It works when switching label and password with sibling css (there is no previous sibling css selector).
HTML:
<md-input-container>
<input type="password" ng-model="password" />
<label>Password</label>
</md-input-container>
CSS (Less) hardcoded:
md-input-container {
.md-input[type=password]:-webkit-autofill ~ label:not(.md-no-float) {
transform: translate3d(0, 6px, 0) scale(0.75);
transition: transform cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s, width cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s;
width: ~"calc((100% - 18px) / 0.75)";
color: rgba(0,0,0,0.54);
}
}
Please note that the decorator as posted earlier throws errors in Firefox since :
can not be used in querySelector(All)
.
Resources: MDN, Mozilla issue #883044.
implement a working fix for this in the framework already.
+1
@Frank3K
i fixed the firefox problem by only activating this to the problematic browser ("chrome"):
(used is_chrome)
$provide.decorator('mdInputContainerDirective', function ($mdTheming, $delegate, $interval) { var is_chrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1; var directive = $delegate[0]; directive.compile = function () { return { post: function ($scope, element, attr, ctrl) { $mdTheming(element); if (is_chrome && ctrl.input && ctrl.input.length && ctrl.input[0].type === 'password') { var count = 0; var interval = $interval(function () { if (count > 10) { $interval.cancel(interval); } if (ctrl.input.parent()[0].querySelector('input:-webkit-autofill')) { ctrl.element.addClass('md-input-has-value'); $interval.cancel(interval); } count++; }, 25); } } }; }; return $delegate; });
Please note that Microsoft Edge also has Chrome
in its user agent string. I'm currently testing for WebkitAppearance
instead.
Isnt the autofill problem is a chrome only issue? Anyway I will check it tomorrow at work on edge too. Please let us know if you find anything.
It is Chrome (or maybe Webkit)-only. I was just pointing out that the method now also runs on Microsoft Edge, where it is not needed.
I simplified the workaround:
app.directive('mdInputContainer', function($timeout) {
return function($scope, element) {
var ua = navigator.userAgent;
if (ua.match(/chrome/i) && !ua.match(/edge/i)) {
$timeout(function() {
if (element[0].querySelector('input[type=password]:-webkit-autofill')) {
element.addClass('md-input-has-value');
}
}, 100);
}
};
});
Thx for sharing :)
..a non TypeScript ver for those who need it:
.directive('mdInputContainer', function ($timeout) {
return function ($scope, element) {
var ua = navigator.userAgent;
if (ua.match(/chrome/i) && !ua.match(/edge/i)) {
$timeout(function () {
if (element[0].querySelector('input[type=password]:-webkit-autofill')) {
element.addClass('md-input-has-value');
}
}, 100);
}
};
});
The workaround works pretty fine, but there are some issues:
required
validator does not behave correctly in the case of auto-filling and if you want to disable your form submission based on form invalidity, it causes a poor user experience (user see button disabled but it gets enabled as soon as they interact with the page).So I changed the code a little to make it more reliable and added a new required
directive which overrides the default required validator to take Chrome auto-filling into account. I wrapped it into a tiny module angular-chrome-autofill-fix
and made it available on bower under the same name.
(function(angular){
"use strict";
var MAX_TRIES = 5;
var TRY_INTERVAL = 100;
angular.module("angular-chrome-autofill-fix", [])
.directive('mdInputContainer', mdInputContainerDirective)
.directive("required", requiredDirective);
function requiredDirective($interval, $log) {
return {
priority: 100,
require: "?ngModel",
link: linkFn
};
function linkFn(scope, elem, attrs, ngModel) {
var originalValidator = ngModel.$validators.required;
ngModel.$validators.required = validator;
// try validating until
var tries = 0;
var timer = $interval(function () {
tries++;
if (tries > MAX_TRIES) {
$interval.cancel(timer);
}
ngModel.$validate();
}, TRY_INTERVAL);
function validator(modelValue, viewValue) {
if (isChrome() && elem.is("input[type=password]:-webkit-autofill")) {
$log.info("bypassing required validator because of Chrome auto-filling");
$interval.cancel(timer);
return true;
}
return originalValidator(modelValue, viewValue);
}
}
}
function mdInputContainerDirective($interval) {
return {
restrict: "E",
link: linkFn
};
function linkFn($scope, elem) {
if (isChrome()) {
var tries = 0;
var timer = $interval(function () {
tries++;
if (tries > MAX_TRIES) {
$interval.cancel(timer);
}
if (elem[0].querySelector('input[type=password]:-webkit-autofill')) {
elem.addClass('md-input-has-value');
$interval.cancel(timer);
}
}, TRY_INTERVAL);
}
}
}
function isChrome(){
return navigator.userAgent.match(/chrome/i) && !navigator.userAgent.match(/edge/i);
}
})(angular);
Can there be a simple fix for this soon?
@camden-kid the project angular-chrome-autofill-fix mentioned just above your comment seems to work fine for me. Give it a try...
still all those fixes over fixes.. just take care of it framework wise already...
To delete the autofill password go to chrome://settings/passwords search the desired url you wish to avoid autofill and click the little x to the right of the stored password. That's the chrome way to fix it.
None of those directives worked. Fortunately @antoinebrault simple adjustment putting label under input and adding this simple css line to your custom css file that should be loaded under the angular-material styles in index.
HTML:
<md-input-container>
<input type="password" ng-model="password" />
<label>Password</label>
</md-input-container>
CSS:
.md-input[type=password]:-webkit-autofill ~ label:not(.md-no-float) {
transform: translate3d(0, 6px, 0) scale(0.75);
transition: transform cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s, width cubic-bezier(0.25, 0.8, 0.25, 1) 0.4s;
width: ~"calc((100% - 18px) / 0.75)";
color: rgba(0,0,0,0.54);
}
From what I can tell there are two classes that should be added dynamically if there is an autofill value in the input one class md-input-has-value
is added to the md-input-container and the second class ng-not-empty
is added to the input. why not with type=password?
This is very easy solution with pure CSS only. Put both Label as well as placeholder and play with the CSS as below:
<md-input-container >
<label>Password</label>
<input ng-model="vm.password" placeholder="Password" id="password" name="password" type="password" required>
</md-input-container>
.md-input-has-placeholder input.ng-empty::-webkit-input-placeholder{ opacity: 0;}
.md-input-invalid input.ng-empty::-webkit-input-placeholder{ opacity: 1;}
.md-input-invalid label{ display: none;}
.md-input-focused label{ display: block;}
.md-input-invalid.md-input-focused input.ng-empty::-webkit-input-placeholder{ opacity: 0;}
.md-input-invalid input.ng-empty::-webkit-input-placeholder{ opacity: 1;}
.md-input-focused input::-moz-placeholder{ opacity: 0;}
@-moz-document url-prefix() { .md-input-has-placeholder label{ display: none;}}
.md-input-has-placeholder.md-input-focused label,.md-input-has-placeholder.md-input-has-value label{ display: block;}
md-input-container.md-input-focused ::-webkit-input-placeholder:not(.md-no-float),md-input-container.md-input-has-placeholder ::-webkit-input-placeholder:not(.md-no-float), md-input-container.md-input-has-value ::-webkit-input-placeholder:not(.md-no-float) {
-webkit-transform: translate3d(0, 6px, 0) scale(0.75);
transform: translate3d(0, 6px, 0) scale(0.75);}
md-input-container.md-input-focused label::-webkit-input-placeholder:not(.md-no-float), md-input-container.md-input-has-placeholder label::-webkit-input-placeholder:not(.md-no-float),md-input-container.md-input-has-value label::-webkit-input-placeholder:not(.md-no-float) {-webkit-transform: translate3d(0, 6px, 0) scale(0.75);
transform: translate3d(0, 6px, 0) scale(0.75);}
Thx @iposton this was the only version that worked for me. The placeholder thing from @Splaktar did not work here.
The solution by @iposton worked to fix the layout issue for me, but it doesn't address the submit button remaining disabled. Still, it's an improvement, and a click on the submit button still results in the form submitting.
Thanks.
+1
I use a similar method to @antoinebrault, absent the timeout.
My form has an autofocus username, with ng-class
defined on the form to call my checkAutoFillValues
method defined in my controller. This seems to reliably correct the issue for me, without using any timeouts or CSS rules. The result of ng-class is basically NOOP, it is just used to trigger checkAutoFillValues
after various events on the form.
html:
<form ng-class="{'auto-fill-checked': checkAutoFillValues()}">
<md-input-container>
<label>Username</label>
<input name="username" required ng-model="auth.username" autofocus/>
... md-messages validation ...
</md-input-container>
<md-input-container>
<label>Password</label>
<input name="password" type="password" required ng-model="auth.password"/>
... md-messages validation ...
</md-input-container>
</form>
controller:
...
$scope.checkAutoFillValues = function() {
// Check if there are auto-filled values on the page and mark them as having a value
$('input:-webkit-autofill').each(function(i, el) {
$(el).parent().addClass('md-input-has-value');
});
}
...
@antoinebrault
This work for me..!!
.input-container input:-webkit-autofill ~ label {
color: #757575;
-webkit-transform: translate(-12%, -50%) scale(0.75);
transform: translate(-12%, -50%) scale(0.75);
}
Thank You.
I had this issue with regular css floating label trick and @SayChamp saved my day :) 馃憤
Trigger resize event on page / app load also fixed this issue
window.dispatchEvent(new Event('resize'));
Most helpful comment
The workaround works pretty fine, but there are some issues:
required
validator does not behave correctly in the case of auto-filling and if you want to disable your form submission based on form invalidity, it causes a poor user experience (user see button disabled but it gets enabled as soon as they interact with the page).So I changed the code a little to make it more reliable and added a new
required
directive which overrides the default required validator to take Chrome auto-filling into account. I wrapped it into a tiny moduleangular-chrome-autofill-fix
and made it available on bower under the same name.