Angular 2 и автозаполнение браузера

Я внедряю страницу входа с реактивными формами 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);
    }

Итак, когда я запускаю его в браузере, у меня есть предварительно заполненные поля "имя пользователя" и "пароли". И в консоли у меня есть значения '{имя_пользователя: "[email protected]", пароль: ""}', и в результате кнопка "login" отключена. Но если я нажимаю где-то на странице, он запускает onValueChanged, и я вижу '{имя_пользователя: "[email protected]", пароль: "123456" }', а кнопка "login" включена.

Если я иду в режиме инкогнито. У меня нет предварительно заполненных полей (они пусты), но когда я заполняю (выбираю) значения, то в консоли я вижу '{имя_пользователя: "[email protected]", пароль: "123456" }' и кнопку "login" включен без дополнительного клика.

Может быть, это разные события? Автозаполнение и автозаполнение? И Angular работает с ними по-другому?

Каков наилучший способ его решения? И почему функция onValueChanged выполняется только один раз, когда поля автозаполнения браузера?

Ответ 1

Проблема в браузере Chrome: он не разрешает доступ к значению поля пароля после автозаполнения (прежде чем пользователь нажимает на страницу). Итак, есть два способа:

  • удалить проверку поля пароля;
  • отключить автозаполнение паролей.

Ответ 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 определяется в модели представления, поскольку бизнес-логика строго отделена от представления в моем приложении.

UPDATE

Так как 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" />

Ответ 3

Я нашел обходной путь

  1. Создайте некоторую переменную isDisabled: boolean = false ОБНОВЛЕНИЕ: 1.1 Еще одна вещь - var initCount: number = 0;

    ngOnInit(): void {this.isDisabled = false; this.initCount++; это 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>

Ответ 4

Для полноты: у меня есть нереактивная форма входа.

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

Таким образом, я могу получить доступ к значениям, предоставленным браузером, как только пользователь нажмет на Login.

Ответ 5

Я обнаружил, что вы можете использовать autocomplete="new-password"

Как описано здесь

Таким образом, поля вашего пароля не будут заполняться автоматически, и это был мой случай. Есть много других атрибутов автозаполнения, доступных по ссылке выше.

Ответ 6

это работает для меня

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

Ответ 7

Я сделал funtion

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

и добавить к кнопке:

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

Это сработало для меня