Angular 2和浏览器自动填充

时间:2017-01-30 09:45:14

标签: angular typescript autocomplete autofill

我正在使用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"和#34;密码"。在控制台中我有值' {userName:" email@email.com" ;,密码:"" }'结果按钮"登录"被禁用。但是,如果我点击页面上的某个位置,它会触发 onValueChanged ,我会看到' {userName:" email@email.com" ;,密码:" 123456& #34; }' ,按钮"登录"已启用。

如果我进入隐身模式。我没有预先填充的字段(它们是空的),但是当我填充(选择)值时,然后在控制台中我看到' {userName:" email@email.com",密码: " 123456" }' ,按钮"登录"无需任何额外点击即可启用。

可能是他们不同的事件?自动填充和自动填充?角度与它们的作用有何不同?

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

9 个答案:

答案 0 :(得分:14)

Chrome浏览器中的问题:在自动填充后(用户点击页面之前),它不允许访问密码字段的值。所以,有两种方法:

  1. 删除密码字段的验证;
  2. 禁用密码自动完成功能。

答案 1 :(得分: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" />

我希望它有所帮助。

注意vmUIComponentBase中定义,并引用了我的组件的视图模型SigninViewModelFormGroup实例在视图模型中定义,因为业务逻辑与我的应用程序中的视图严格分开。

<强>更新

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

答案 2 :(得分:2)

我找到了一些解决方法

  1. 创建一些var isDisabled:boolean = false 更新: 1.1还有一件事 - var initCount:number = 0;

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

  2. 创建方法

  3. 这是代码:

    // 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. 用以下内容更新你的HTML:
    2. 以下是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>
      

答案 3 :(得分:2)

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

被描述为here

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

答案 4 :(得分: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;
  ...

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

答案 5 :(得分:0)

这对我有用

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类

答案 6 :(得分:0)

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

尝试一下。使我的脑袋有些折腾,但这成功了。

input:-webkit-autofill {
  -webkit-transition-delay: 99999s;
}

答案 7 :(得分:0)

我的解决方案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;

答案 8 :(得分:-3)

我做了一个功能

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

然后添加到按钮:

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

这对我有用