38

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>
niico
  • 11,206
  • 23
  • 78
  • 161
Catfish
  • 18,876
  • 54
  • 209
  • 353
  • did you know this post: http://stackoverflow.com/questions/11708092/detecting-browser-autofill ? – michael Jan 16 '14 at 19:27
  • Yes I have. I'm doing what he suggests by setting `autocomplete="off"` but it's not working. – Catfish Jan 16 '14 at 20:01
  • one more idea that seems to work. i have separated the fields in two forms. form1 and form2 the password field is never autofilled again. – michael Jan 16 '14 at 20:22
  • Interesting. Definitely a hack to make it work, but I fear there is no good solutions to this from my research... – Catfish Jan 16 '14 at 20:24
  • and next try: angular did not need the form element. you may use a div with ng-form="formName", same result: no autocompletion. – michael Jan 16 '14 at 20:27
  • did you try this directive: http://stackoverflow.com/a/21073112/149060 (similar to @kfis answer)? – Pauli Price Jan 29 '14 at 14:00
  • This one: https://github.com/angular/angular.js/issues/1460#issuecomment-32491109 would appear to be even more complete. Fortunately, there appears to be a fix recently committed to Angularjs for this issue. – Pauli Price Jan 29 '14 at 14:06
  • Solution is found here http://stackoverflow.com/a/29582380/4462191 to disable browser autofill – camiblanch May 06 '15 at 15:47

15 Answers15

21

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);
            }
        }
    }
}]);
Kondal
  • 2,870
  • 5
  • 26
  • 40
Pauli Price
  • 4,187
  • 3
  • 34
  • 62
  • 2
    +1 This is the best solution on this page because it doesn't disable autocomptlete. Disabling autocomplete is like saying "hey user, screw you and your preferences". The only cross-browser solution is to poll. It's not difficult, and someone already wrote the angular-friendly code... – Ryan Wheale Jan 29 '14 at 18:49
  • 5
    `link.apply` throws error. `ngModel` isn't passed through since `compile` function doesn't accept it and therefore `$setViewValue` doesn't work. – Eugene Jan 17 '16 at 17:59
  • Hi I'm new to angularjs, after adding the above code in js file and including it in the HTML file, I'm getting this error Uncaught Error: [$injector:nomod] http://errors.angularjs.org/1.4.8/$injector/nomod?p0=myApp Could you please help me out? – anujeet Sep 09 '16 at 10:58
  • Is it the best solution for Angular2 as well? It seems that when the user chooses a Chrome's suggestion to fill the form, the model doesn't pick up changes. – Alex Klaus Sep 28 '16 at 04:03
16

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

Arve Systad
  • 5,471
  • 1
  • 32
  • 58
ptgamr
  • 584
  • 7
  • 13
  • I know the question was about angularjs. But why add code to fix the issue when you can add an attribute to the html? – Campinho Aug 30 '17 at 22:19
  • "new-password" seems to work better overall than "off". Both "email" and "password" types got empty input values. – Steffo Dimfelt Jan 02 '23 at 13:01
12

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

allenhwkim
  • 27,270
  • 18
  • 89
  • 122
  • The question is related to AngularJS in case there is a solution to capturing the auto-fill text in Angularjs. I was not aware of having to set autocomplete off on the input's though. – Catfish Jan 22 '14 at 20:46
  • 2
    I tried setting autocomplete="off" on both the form and the input elements and the auto complete is still autocompleting and the value still does not get set to my angular model. – Catfish Jan 24 '14 at 00:06
  • Sad this doesn't work because even the Chrome notes here https://bugs.chromium.org/p/chromium/issues/detail?id=252609 say to use `autocomplete="off"` – Luke Sep 25 '16 at 13:49
7

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.

Dachande663
  • 504
  • 1
  • 6
  • 16
  • 2
    Controllers actually should not have that kind of jquery code. Avoid accessing the DOM from there, thats why directives are there. This kind of code might break your testing code. – Christian Apr 30 '14 at 07:28
  • 5
    And for anywhere else I'd agree with you. But this is a browser quirk that a headless phantomjs instance wouldn't pick up anyway. – Dachande663 Apr 30 '14 at 12:49
2

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>
                       -----------------------------
kfis
  • 4,739
  • 22
  • 19
2

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>
Kondal
  • 2,870
  • 5
  • 26
  • 40
FullStackDev
  • 1,569
  • 1
  • 10
  • 7
2

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

RafaelPlantard
  • 117
  • 1
  • 7
  • 3
    on/off seem to be the HTML5 standard for the `autocomplete`property. https://www.w3.org/wiki/HTML/Elements/input/text – Luke Sep 25 '16 at 13:52
1

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()">
Matej Janovčík
  • 1,242
  • 12
  • 13
  • > Note: ngForm cannot be used as a replacement for
    , because it lacks its built-in HTML functionality. Specifically, you cannot submit ngForm like a
    tag. That means, you cannot send data to the server with ngForm, or integrate it with ngSubmit. https://docs.angularjs.org/api/ng/directive/ngForm
    – no id Nov 25 '18 at 22:34
  • Of course you can send data to the server with ngForm, you just need to write custom logic, as you correctly mentioned it doesn't have built-in HTML form functionality – Matej Janovčík Dec 13 '18 at 22:38
0

--- NO LONGER RELEVANT ---

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

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

Reference this Plunker

Cory Silva
  • 2,761
  • 17
  • 23
0

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?

jpaugh
  • 6,634
  • 4
  • 38
  • 90
110maor
  • 61
  • 1
  • 2
  • 4
0

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.

Louis Boux
  • 1,228
  • 1
  • 12
  • 26
0

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

<form autocomplete="off">
   <input type="text" autocomplete="off">
</form>
Kate.spot
  • 11
  • 3
0

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

```

Joel Chu
  • 828
  • 1
  • 9
  • 25
0

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.

bmm6o
  • 6,187
  • 3
  • 28
  • 55
-1

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

Krishan Kumar
  • 394
  • 4
  • 13