Angular 2 和浏览器自动填充

27

我正在使用Angular响应式表单实现登录页面。如果表单无效,按钮“登录”将被禁用。

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:""}', 结果按钮“登录”被禁用。但是,如果我在页面上点击某个地方,它会触发 onValueChanged ,然后我看到'{ userName:“email@email.com”,password:“123456”}', 按钮“登录”就可以使用了。

如果我进入无痕模式。我没有预填充的字段(它们是空的),但是当我填写(选择)值时,然后在控制台中我会看到'{ userName:“email@email.com”,password:“123456”}', 按钮“登录”会直接启用,而不需要任何额外的点击。

也许它们是不同的事件?自动填充和自动完成?并且angular以不同的方式处理它们吗?

解决它的最佳方法是什么?为什么只有在浏览器自动填充字段时 onValueChanged 函数才会执行一次?

10个回答

21
Chrome 浏览器的问题:自动填充后(在用户点击页面之前),不允许访问密码字段的值。因此,有两种解决方法:
  1. 删除密码字段验证;
  2. 禁用密码自动完成。

6
@Denish autocomplete="off" - aiven
如果我们在Chrome中启用了保存密码弹出窗口,那么autocomplete="off"将不起作用。 - giveJob
请参考此链接:https://developer.mozilla.org/zh-CN/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion - Pranjal Successena
1
我发现使用autocomplete="new-password"可以防止Chrome自动填充。 - Nasta
禁用自动完成,请在此处查看答案。希望它能起作用。 https://stackoverflow.com/a/68343782/5107668 - Okasha Rafique
显示剩余3条评论

11

我发现你可以使用autocomplete="new-password"

正如这里所描述的那样。

这样你的密码字段将不会被自动填充,这就是我的情况。上述链接中还有许多其他可用的自动完成属性。


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(现在不会自动填充,但工作正常)

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

:hide 是 Bootstrap v4 的类名。


class"hide" 对我不起作用,我使用 class="invisible" 它完美地工作。 - Ousama
另外一件事,它在生产环境中不起作用。 - Ousama

2
我找到了一些解决方法:
  1. 创建一个变量isDisabled:boolean = false 更新: 1.1 还有一件事 - 创建变量initCount: number = 0;

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

  2. 创建方法

以下是代码:

// 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,类似于以下内容:
```
  1. 这里是一个示例
```
这是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

我在使用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" />

1

为了完整起见:我有一个非响应式的登录表单。

<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;
  ...

这样,一旦用户点击“登录”,我就可以访问浏览器提供的值。

1
我的解决方案2019版本:

Gist Hub

,其中保留了HTML标记。
// <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;

0
我通过在输入标签中添加属性autocomplete="new-feildName"解决了这个问题。 示例:
<input type="text" class="mr-input" formControlName="email" 
placeholder="Adresse e-mail" autocomplete="new-email"/>

注意:您应该为每个字段添加自动完成功能,以便自动填充。


0

-2
我创建了一个函数: formIsValid(): boolean{ return this.form.valid; } 然后将其添加到按钮中:
<button [disabled]="!formIsValid()" type="submit">LogIn</button>

这对我有用


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接