Angular 2 and browser autofill
Asked Answered
N

10

27

I'm implementing login page with Angular reactive forms. Button "login" is disabled if form is invalid.

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
    selector: 'signin',
    templateUrl: './signin.component.html'
})
export class SignInComponent implements OnInit {
    private signInForm: FormGroup;

    constructor(private formBuilder: FormBuilder) { }

    ngOnInit() {
        this.buildForm();
    }

    private buildForm(): void {
        this.signInForm = this.formBuilder.group({
            userName: ['', [Validators.required, Validators.maxLength(50)]],
            password: ['', [Validators.required, Validators.maxLength(50)]]
        });

        this.signInForm.valueChanges
            .subscribe((data: any) => this.onValueChanged(data));

        this.onValueChanged();
    }

    private onValueChanged(data?: any) {
        console.log(data);
    }

So, when I launch it in browser, I have prepopulated fields "userName" and "passwords". And in console I have values '{ userName: "[email protected]", password: "" }' and as a result button "login" is disabled. But if I click somewhere on page it trigger onValueChanged and I see '{ userName: "[email protected]", password: "123456" }', and button "login" is enabled.

If I go in incognito mode. I have no prepopulated fields (they are empty), but when I populate (select) values, then in console I see '{ userName: "[email protected]", password: "123456" }', and button "login" is enabled without any extra click.

May be they are different events? Autofill and autocomplete? And angular works with them differently?

What is the best way to resolve it? And why onValueChanged function is executed only once when browser autofill fields?

Nafis answered 30/1, 2017 at 9:45 Comment(0)
S
21

The problem in Chrome browser: it does not allow access to the value of the password field after autofill (before user click on the page). So, there are two ways:

  1. remove validation of the password field;
  2. disable password autocompletion.
Sessile answered 31/1, 2017 at 14:4 Comment(8)
Unfortunely password autocompletion doesn't seem to work. Tried all 3 possible values: autocomplete="none|false|no"Lawrence
@Lawrence autocomplete="off"Quarterhour
if we enable save password popup from chrome then autocomplete="off" doesnot workPapyraceous
autocomplete="off" will ask for saving the credentials for the first time and if the users opts for it, next time autocomplete="off" won't work and you will see the credentials already filledPinstripe
refer this link developer.mozilla.org/en-US/docs/Web/Security/…Pinstripe
I found that using autocomplete="new-password" works to prevent chrome autofill.Aircool
Looks like it's not only about password fields but any input fields. So I had this exact problem and all my fields were marked as required. I removed this attribute and it all works as it's supposed to. Thanks!Shirlyshiroma
To disable autocomplete view the answer here. hope so it will work https://mcmap.net/q/393810/-angular-2-remove-auto-save-username-and-passwordManse
A
11

I found out that you can use autocomplete="new-password"

As is decribed here

This way your password fields will not be autofilled at all, and that was my case. There are many others autocomplete attributes available in the link above.

Arbitrator answered 4/9, 2018 at 1:40 Comment(0)
P
3

this is worked for me

1. before(it auto filling)

<div class="form-group form-row required">
     <input class="input p-l-20 form-control" formControlName='otp' type="text" name="otp" placeholder="Enter OTP">
</div>

2. after(now not auto filling, working fine)

<div class="form-group form-row required">
       <input class="input p-l-20 form-control" formControlName='otp' type="text" name="otp" placeholder="Enter OTP">
       <!-- this dummy input is solved my problem -->
       <input class="hide"  type="text">
</div>

note hide is bootstrap v4 class

Papyraceous answered 8/12, 2018 at 5:52 Comment(2)
class"hide" doesn't work for me, I used class="invisible" it works perfectly.Sermonize
Another thing, it doesn't work in production.Sermonize
S
2

I faced the same issue with Chrome v58.0.3029.96. Here is my workaround to keep validation and autocompletion:

export class SigninComponent extends UIComponentBase
{
    //--------------------------------------------------------------------------------------------
    // CONSTRUCTOR
    //--------------------------------------------------------------------------------------------
    constructor(viewModel: SigninViewModel)
    {
        super(viewModel);

        // Autofill password Chrome bug workaround
        if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1)
        {
            this._autofillChrome = true;
            this.vm.FormGroup.valueChanges.subscribe(
                (data) =>
                {
                    if (this._autofillChrome && data.uname)
                    {
                        this._password = " ";
                        this._autofillChrome = false;
                    }
                });
        }
    }

    //--------------------------------------------------------------------------------------------
    // PROPERTIES
    //--------------------------------------------------------------------------------------------
    private _password: string;
    private _autofillChrome: boolean;

    //--------------------------------------------------------------------------------------------
    // COMMAND HANDLERS
    //--------------------------------------------------------------------------------------------
    private onFocusInput()
    {
        this._autofillChrome = false;
    }
}

And in my html:

<input mdInput 
       [placeholder]="'USERNAME_LABEL' | translate" 
       [formControl]="vm.FormGroup.controls['uname']" 
       (focus)="onFocusInput()" />
[...]
<input mdInput 
       type="password" 
       [placeholder]="'PASSWORD_LABEL' | translate" 
       [formControl]="vm.FormGroup.controls['password']" 
       (focus)="onFocusInput()"
       [(ngModel)]="_password" />

I hope it helps.

Note: vm is defined in UIComponentBase and refers to the view-model SigninViewModel of my component. The FormGroup instance is defined in the view-model as the business logic is strictly separated from the view in my application.

UPDATE

Since v59, it seems that the input's focus event is fired when auto-completing fields. So the above workaround doesn't work anymore. Here is the updated workaround, using a timeout to determine if fields are updated by the auto-completion or by the user (I haven't found a better way):

export class SigninComponent extends UIComponentBase
{
    //--------------------------------------------------------------------------------------------
    // CONSTRUCTOR
    //--------------------------------------------------------------------------------------------
    constructor(viewModel: SigninViewModel)
    {
        super(viewModel);

        // Autofill password Chrome bug workaround
        if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1)
        {
            this._autofillChrome = true;
            setTimeout(
                () =>
                {
                    this._autofillChrome = false;
                },
                250 // 1/4 sec
            );
            this.vm.FormGroup.valueChanges.subscribe(
                (data) =>
                {
                    if (this._autofillChrome && data.uname)
                    {
                        this._password = " ";
                        this._autofillChrome = false;
                    }
                });
        }
    }

    //--------------------------------------------------------------------------------------------
    // PROPERTIES
    //--------------------------------------------------------------------------------------------
    private _password: string;
    private _autofillChrome: boolean;
}

And in my html:

<input mdInput 
       [placeholder]="'USERNAME_LABEL' | translate" 
       [formControl]="vm.FormGroup.controls['uname']" />
[...]
<input mdInput 
       type="password" 
       [placeholder]="'PASSWORD_LABEL' | translate" 
       [formControl]="vm.FormGroup.controls['password']" 
       [(ngModel)]="_password" />
Shortcoming answered 8/5, 2017 at 20:22 Comment(0)
D
2

I've found some workaround

  1. Create some var isDisabled: boolean = false UPDATE: 1.1 One more thing - var initCount: number = 0;

    ngOnInit(): void { this.isDisabled = false; this.initCount++; }

  2. Create methods

And this is the code:

// This method will be called async
onStart(): Observable<boolean> {
    if (this.loginFomrControl.hasError('required') && 
    this.passwordFormControl.hasError('required') && this.initCount == 1) {
      this.isDisabled = false;

      // increase init count so this method wound be called async
      this.initCount++;
    }

    return new BehaviorSubject<boolean>(true).asObservable();
  }

// This method will ve called on change
validateForm() {
    if (this.loginFormControl.value != '' && this.passwordFormControl.value != '') {
      this.isDisabled = false;
    } else if (this.loginFomrControl.value == '' || this.passwordFormControl.value == '') {
      this.isDisabled = true;
    }
  }
  1. Update you html with something like this:

Here is a sample of the HTML

<div class="container" *ngIf="onStart() | async">
<!-- Do Stuff -->
  <mat-form-field class="login-full-width">
    <input matInput placeholder="{{ 'login_email' | translate }}" id="login_email" [formControl]="loginFomrControl" (change)="validateForm()">
    <mat-error *ngIf="loginFomrControl.hasError('email') && !loginFomrControl.hasError('required')">
            {{'login_email_error' | translate}}
    </mat-error>
    <mat-error *ngIf="loginFomrControl.hasError('required')">
            {{ 'login_email' | translate }}
        <strong>{{ 'login_required' | translate }}</strong>
    </mat-error>
  </mat-form-field>
<!-- Do Stuff -->
</div>
Dario answered 17/4, 2018 at 17:22 Comment(0)
H
1

For the sake of completeness: I have a non-reactive login form.

<form name="loginForm" role="form" (ngSubmit)="onLoginSubmit()">
  <input name="username" #usernameInp>
  <input type="password" name="password" #passwordInp>
  <button type="submit">Login</button>
</form>

If I use [(ngModel)]="username", the variable username of the associated component does not get populated if the browser autofill functionality kicks in.

Therefore, I instead work with ViewChild:

@ViewChild('usernameInp') usernameInp: ElementRef;
@ViewChild('passwordInp') passwordInp: ElementRef;
...
onLoginSubmit() {
  const username = this.usernameInp.nativeElement.value;
  const password = this.passwordInp.nativeElement.value;
  ...

This way, I can access the values provided by the browser once the user clicks on Login.

Homeric answered 21/2, 2018 at 10:25 Comment(0)
A
1

my solution 2019 version: Gist Hub

// <ion-input (change)="fixAutoFill($event, 'password')" #passwordInput autocomplete="off" formControlName="password" type="password"></ion-input>
// <ion-input (change)="fixAutoFill($event, 'username')" #usernameInput autocomplete="off" type="text" formControlName="username"></ion-input>


  fixAutoFill(event, type) {
    const value = event.target.value;
    if (type === 'username') {
      this.loginForm.patchValue({
        username: value
      }, {emitEvent: true, onlySelf: false});
    } else {
      this.loginForm.patchValue({
        password: value
      }, {emitEvent: true, onlySelf: false});
    }
    // console.log('after click', this.loginForm.value);
  }

  @ViewChild('usernameInput') usernameInput: IonInput;
  @ViewChild('passwordInput') passwordInput: IonInput;
Acquit answered 6/8, 2019 at 7:9 Comment(0)
P
0

http://webagility.com/posts/the-ultimate-list-of-hacks-for-chromes-forced-yellow-background-on-autocompleted-inputs

Try this out. Racked my brain for a bit but this did the trick.

input:-webkit-autofill {
  -webkit-transition-delay: 99999s;
}
Preestablish answered 15/12, 2018 at 19:19 Comment(0)
S
0

I solved this issue by adding the attribute autocomplete="new-feildName" to input tag.

Example:

<input type="text" class="mr-input" formControlName="email" 
placeholder="Adresse e-mail" autocomplete="new-email"/>

Note: you should add autocomplete to every field is filled automatically.

Sermonize answered 15/4, 2022 at 10:36 Comment(0)
F
-1

I made a funtion

formIsValid(): boolean{ return this.form.valid; }

and than add to the button:

<button [disabled]="!formIsValid()" type="submit">LogIn</button>

It's worked for me

Fennie answered 12/1, 2018 at 18:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.