【问题标题】:Angular 2 and browser autofillAngular 2 和浏览器自动填充
【发布时间】:2017-06-15 10:15:39
【问题描述】:

我正在使用 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:“email@email.com”,密码:“”}',因此按钮“登录”被禁用。但是,如果我点击页面上的某个位置,它会触发 onValueChanged,我会看到 '{ userName: "email@email.com", password: "123456" }', and button "登录”已启用。

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

它们可能是不同的事件吗?自动填充和自动完成? Angular 对它们的处理方式不同?

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

【问题讨论】:

    标签: angular typescript autocomplete autofill


    【解决方案1】:

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

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

    【讨论】:

    • 不幸的是,密码自动完成似乎不起作用。尝试了所有 3 个可能的值:autocomplete="none|false|no"
    • @Denish autocomplete="off"
    • 如果我们从 chrome 启用保存密码弹出窗口,那么 autocomplete="off" 不起作用
    • autocomplete="off" 将首次要求保存凭据,如果用户选择保存,下次 autocomplete="off" 将不起作用,您将看到凭据已填写
    • 我发现使用 autocomplete="new-password" 可以防止 chrome 自动填充。
    【解决方案2】:

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

    正如here所描述的那样

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

    【讨论】:

      【解决方案3】:

      我在 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 中,指的是我的组件的视图模型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" />
      

      【讨论】:

        【解决方案4】:

        我找到了一些解决方法

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

          ngOnInit(): 无效 { 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;
            }
          }
        
        1. 用这样的方式更新你的 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>
        

        【讨论】:

          【解决方案5】:

          这对我有用

          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>
          

          注意隐藏是引导 v4 类

          【讨论】:

            【解决方案6】:

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

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

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

            【讨论】:

              【解决方案7】:

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

              【讨论】:

                【解决方案8】:

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

                试试这个。绞尽脑汁,但这成功了。

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

                【讨论】:

                  【解决方案9】:

                  我做了一个函数

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

                  然后添加到按钮:

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

                  对我有用

                  【讨论】:

                    猜你喜欢
                    • 2017-04-09
                    • 2013-04-04
                    • 2018-05-23
                    • 2012-07-27
                    • 2012-03-03
                    • 2012-04-17
                    相关资源
                    最近更新 更多