Angularjs Chrome autocomplete dilemma

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>

Answers:

Answer

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);
            }
        }
    }
}]);
Answer

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 :)

Answer

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

Answer

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.

Answer

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>
                       -----------------------------
Answer

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

Answer

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>
Answer

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()">
Answer

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.

Answer

In my case, i set property autocomplete="off" in form and input.

<form autocomplete="off">
   <input type="text" autocomplete="off">
</form>
Answer

It could be much simpler solution to the problem.

  1. Angularjs couldn't "see" the value
  2. 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;
            });
        }
    };
});

```

Answer

--- NO LONGER RELEVANT ---

I was able to disable autocomplete (weirdly enough) by adding the following.

<form ... novalidate>
<input ... formnovalidate />

Reference this Plunker

Answer

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?

Answer

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.

Answer

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

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.