Angular 2 和浏览器自动填充
Angular 2 and browser autofill
我正在使用 Angular 响应式表单实现登录页面。如果表单无效,按钮 "login" 将被禁用。
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);
}
因此,当我在浏览器中启动它时,我已经预填充了字段 "userName" 和 "passwords"。在控制台中,我有值 '{ userName: "email@email.com", password: "" }',因此按钮 "login" 被禁用。但是,如果我点击页面上的某处,它会触发 onValueChanged 并且我会看到 '{ userName: "email@email.com", password: "123456" }' ,并且按钮 "login" 已启用。
如果我进入隐身模式。我没有预填充的字段(它们是空的),但是当我填充 (select) 值时,然后在控制台中我看到 '{ userName: "email@email.com", password: "123456" } ',按钮 "login" 无需任何额外点击即可启用。
可能是不同的事件?自动填充和自动完成? angular 与他们的工作方式不同?
最好的解决方法是什么?为什么 onValueChanged 函数在浏览器自动填充字段时只执行一次?
Chrome 浏览器中的问题:它不允许在自动填充后访问密码字段的值(在用户单击页面之前)。所以,有两种方法:
- 删除密码字段的验证;
- 禁用密码自动完成。
我在使用 Chrome v58.0.3029.96 时遇到了同样的问题。这是我保持验证和自动完成的解决方法:
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;
}
}
在我的 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" />
希望对您有所帮助。
注意:vm
在UIComponentBase
中定义,指的是我的组件的视图模型SigninViewModel
。 FormGroup
实例在视图模型中定义,因为业务逻辑与我的应用程序中的视图严格分开。
更新
从 v59 开始,输入的焦点事件似乎在自动完成字段时被触发。所以上面的解决方法不再有效。这是更新的解决方法,使用超时来确定字段是由自动完成还是由用户更新的(我还没有找到更好的方法):
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;
}
在我的 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" />
我做了一个函数
formIsValid(): boolean{
return this.form.valid;
}
然后添加到按钮:
<button [disabled]="!formIsValid()" type="submit">LogIn</button>
对我有用
为了完整起见:我有一个 non-reactive 登录表单。
<form name="loginForm" role="form" (ngSubmit)="onLoginSubmit()">
<input name="username" #usernameInp>
<input type="password" name="password" #passwordInp>
<button type="submit">Login</button>
</form>
如果我使用 [(ngModel)]="username"
,如果浏览器 自动填充 功能启动,关联组件的变量 username
不会被填充。
因此,我改为使用 ViewChild:
@ViewChild('usernameInp') usernameInp: ElementRef;
@ViewChild('passwordInp') passwordInp: ElementRef;
...
onLoginSubmit() {
const username = this.usernameInp.nativeElement.value;
const password = this.passwordInp.nativeElement.value;
...
这样,一旦用户单击 登录,我就可以访问浏览器提供的值。
我找到了一些解决方法
创建一些 var isDisabled: boolean = false
更新:
1.1 还有一件事 - var initCount: number = 0;
ngOnInit(): void {
this.isDisabled = 假;
this.initCount++;
}
创建方法
这是代码:
// 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;
}
}
- 更新你 html 像这样:
这是 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>
我发现你可以使用 autocomplete="new-password"
正如描述的那样here
这样一来,您的密码字段就不会被自动填充,我就是这样。
在上面的 link 中还有许多其他可用的自动完成属性。
这对我有用
1.之前(自动填充)
<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。之后(现在不是自动填充,工作正常)
<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>
注意 隐藏是 bootstrap v4 class
试试这个。绞尽脑汁想了一下,但还是成功了。
input:-webkit-autofill {
-webkit-transition-delay: 99999s;
}
我的解决方案 2019 版本: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;
我通过将属性 autocomplete="new-feildName"
添加到输入标签解决了这个问题。
示例:
<input type="text" class="mr-input" formControlName="email"
placeholder="Adresse e-mail" autocomplete="new-email"/>
注意:您应该为自动填充的每个字段添加自动完成。
我正在使用 Angular 响应式表单实现登录页面。如果表单无效,按钮 "login" 将被禁用。
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);
}
因此,当我在浏览器中启动它时,我已经预填充了字段 "userName" 和 "passwords"。在控制台中,我有值 '{ userName: "email@email.com", password: "" }',因此按钮 "login" 被禁用。但是,如果我点击页面上的某处,它会触发 onValueChanged 并且我会看到 '{ userName: "email@email.com", password: "123456" }' ,并且按钮 "login" 已启用。
如果我进入隐身模式。我没有预填充的字段(它们是空的),但是当我填充 (select) 值时,然后在控制台中我看到 '{ userName: "email@email.com", password: "123456" } ',按钮 "login" 无需任何额外点击即可启用。
可能是不同的事件?自动填充和自动完成? angular 与他们的工作方式不同?
最好的解决方法是什么?为什么 onValueChanged 函数在浏览器自动填充字段时只执行一次?
Chrome 浏览器中的问题:它不允许在自动填充后访问密码字段的值(在用户单击页面之前)。所以,有两种方法:
- 删除密码字段的验证;
- 禁用密码自动完成。
我在使用 Chrome v58.0.3029.96 时遇到了同样的问题。这是我保持验证和自动完成的解决方法:
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;
}
}
在我的 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" />
希望对您有所帮助。
注意:vm
在UIComponentBase
中定义,指的是我的组件的视图模型SigninViewModel
。 FormGroup
实例在视图模型中定义,因为业务逻辑与我的应用程序中的视图严格分开。
更新
从 v59 开始,输入的焦点事件似乎在自动完成字段时被触发。所以上面的解决方法不再有效。这是更新的解决方法,使用超时来确定字段是由自动完成还是由用户更新的(我还没有找到更好的方法):
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;
}
在我的 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" />
我做了一个函数
formIsValid(): boolean{
return this.form.valid;
}
然后添加到按钮:
<button [disabled]="!formIsValid()" type="submit">LogIn</button>
对我有用
为了完整起见:我有一个 non-reactive 登录表单。
<form name="loginForm" role="form" (ngSubmit)="onLoginSubmit()">
<input name="username" #usernameInp>
<input type="password" name="password" #passwordInp>
<button type="submit">Login</button>
</form>
如果我使用 [(ngModel)]="username"
,如果浏览器 自动填充 功能启动,关联组件的变量 username
不会被填充。
因此,我改为使用 ViewChild:
@ViewChild('usernameInp') usernameInp: ElementRef;
@ViewChild('passwordInp') passwordInp: ElementRef;
...
onLoginSubmit() {
const username = this.usernameInp.nativeElement.value;
const password = this.passwordInp.nativeElement.value;
...
这样,一旦用户单击 登录,我就可以访问浏览器提供的值。
我找到了一些解决方法
创建一些 var isDisabled: boolean = false 更新: 1.1 还有一件事 - var initCount: number = 0;
ngOnInit(): void { this.isDisabled = 假; this.initCount++; }
创建方法
这是代码:
// 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;
}
}
- 更新你 html 像这样:
这是 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>
我发现你可以使用 autocomplete="new-password"
正如描述的那样here
这样一来,您的密码字段就不会被自动填充,我就是这样。 在上面的 link 中还有许多其他可用的自动完成属性。
这对我有用
1.之前(自动填充)
<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。之后(现在不是自动填充,工作正常)
<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>
注意 隐藏是 bootstrap v4 class
试试这个。绞尽脑汁想了一下,但还是成功了。
input:-webkit-autofill {
-webkit-transition-delay: 99999s;
}
我的解决方案 2019 版本: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;
我通过将属性 autocomplete="new-feildName"
添加到输入标签解决了这个问题。
示例:
<input type="text" class="mr-input" formControlName="email"
placeholder="Adresse e-mail" autocomplete="new-email"/>
注意:您应该为自动填充的每个字段添加自动完成。