Angular 2 and browser autocomplete

I am embedding an Angular reactive forms login page. The login button is disabled if the form is not valid.

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 the browser, I have the pre-populated "username" and "passwords" fields. And in the console, I have the values ​​'{username: "email@email.com", password: ""}', and as a result the "login" button is disabled. But if I click somewhere on the page, it launches onValueChanged , and I see '{username: "email@email.com", password: "123456"}', and the "login" button is turned on.

If I go incognito. I don’t have any pre-filled fields (they are empty), but when I fill out (select) the values, in the console I see '{username: "email@email.com", password: "123456"}' and the "login" button is turned on without an extra click.

Maybe these are different events? Autofill and autofill? And does Angular work with them differently?

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

+14
autocomplete angular autofill typescript
source share
7 answers

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

  • remove password field verification;
  • disable password autofill.
+13
source share

I had the same problem with Chrome v58.0.3029.96. Here is my workaround for maintaining validation and autocomplete:

 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" /> 

Hope this helps.

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

UPDATE

Since v59, it seems that the input focal event is fired when the fields are autocomplete. Thus, the above workaround no longer works. Below is an updated workaround using a timeout to determine if the fields were updated by autocomplete or by the user (I did not find 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" /> 
+2
source share

I found a workaround

  1. Create some variable isDisabled: boolean = false UPDATE: 1.1 Another thing is var initCount: number = 0;

    ngOnInit (): void {this.isDisabled = false; 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 your html something like this:

Here is a sample 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> 
+2
source share

For 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 username variable of the associated component is not populated if the browser autofill function is started.

So I work with ViewChild instead:

 @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 as soon as the user clicks on Login.

0
source share

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

As described here

Thus, the fields of your password will not be filled in automatically, and this was my case. There are many other autocomplete attributes available at the link above.

0
source share

it works for me

1. before (this is autocomplete)

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

2. after (now not autocomplete, it works fine)

 <div class="form-group form-row required"> <input class="input pl-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 this is v4 bootstrap class

0
source share

I made funtion

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

and add to the button:

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

It worked for me

-2
source share

All Articles