Предотвратить раннюю проверку при использовании автозаполнения и updateOn: «размытие»

В моем приложении Angular используются сложные проверки, выполняемые на стороне сервера. По этой причине я настроил обновления и проверки только для событий blur:

this.form = new FormGroup(
    { ... },
    {
        updateOn: 'blur'
    }
);

Он работает хорошо, за исключением полей, которые используют автозаполнение. Если автодополнение открыто и пользователь щелкает запись мышью, происходит неприятная последовательность событий:

  • Запускается событие размытие
  • Проверка выполняется с неполным старым значением и добавляет ошибку
  • Выбранное значение автозаполнения помещается в поле
  • Всплывающее окно автозаполнения закрывается, и поле снова получает фокус

Результат выглядит так, как показано ниже (упрощенный пример). Допустимое значение находится в текстовом поле, но оно помечено как ошибка, поскольку проверка выполнялась для старого значения.

введите здесь описание изображения

Технически правильно, что проверка была запущена, потому что щелчок по всплывающему окну автозаполнения вызывает событие blur. Однако с точки зрения пользовательского интерфейса это ерунда. Проверка должна произойти, когда вы закончите с полем и перейдете к следующему полю.

Итак, как я могу предотвратить событие blur и раннюю проверку?

Я создал простой пример StackBlitz. Он использует аналогичную настройку, но выполняет проверку на стороне клиента (и проверяет, что текст начинается с «ABC»). Чтобы воспроизвести проблему, введите «34», а затем выберите «ABC34» во всплывающем окне автозаполнения с помощью мыши.


person Codo    schedule 23.06.2018    source источник


Ответы (1)


для срабатывания при изменении символа мы должны инициировать событие ввода, а также событие изменения автозаполнения, поэтому вы можете попробовать что-то вроде этого:

в компоненте:

import { Component, OnInit , ViewChild , ElementRef} from '@angular/core';
import { VERSION } from '@angular/material';
import { FormGroup, FormControl } from '@angular/forms';
import { Observable, Subject } from 'rxjs';
import { startWith, map } from 'rxjs/operators';

@Component({
  selector: 'material-app',
  templateUrl: 'app.component.html'
})
export class AppComponent implements OnInit {


  @ViewChild('textInput') textInput: ElementRef;  




  version = VERSION;
  form: FormGroup;
  abcText: string = 'ABC1';
  anyText: string = '';
  public readonly abcChanges: Subject<string> = new Subject<string>();
  public abcSuggestions: Observable<string[]>;

  ngOnInit() {
    this.form = new FormGroup({
      abcText: new FormControl(this.abcText),
      anyText: new FormControl(this.anyText)
    }, {
        updateOn: 'blur'
      });

    this.form.valueChanges.subscribe(val => {
      this.validateData(val)}

    );

    this.abcSuggestions = this.abcChanges.pipe(
      startWith(''),
      map(val => this.generateSuggestions(val))
    );
  }

  private validateData(val: any) {
    console.log(val)
    // Would be more complex and happen on the server side
    const text: string = val['abcText'];
    const formControl = this.form.get('abcText');
    if (text.startsWith('ABC')) {
      formControl.setErrors(null);
    } else {
      formControl.setErrors({ abc: 'Must start with ABC' });
    }
  }

  private generateSuggestions(val: string) {
    let suggestions = [];
    if (!val.startsWith('ABC')) {
      suggestions.push('ABC' + val);
    }
    suggestions.push('ABC1');
    suggestions.push('ABC2');
    suggestions.push('ABC3');
    return suggestions;
  }

    validateOnCharacterChange(value) {
    console.log(value)
    const formControl = this.form.get('abcText');

    if (value.startsWith('ABC')) {
      formControl.setErrors(null);
    } else {
      formControl.setErrors({ abc: 'Must start with ABC' });
    }
    // this.textInput.nativeElement.blur();
  }
}

в html:

<mat-toolbar color="primary">
    Angular Material 2 App
</mat-toolbar>
<div class="basic-container">
    <form [formGroup]="form" novalidate>
        <div>
            <mat-form-field>
                <input matInput [matAutocomplete]="auto" formControlName="abcText" (input)="abcChanges.next($event.target.value)" placeholder="Text starting with ABC" #textInput required (input)="validateOnCharacterChange($event.target.value)">
                <mat-error>Must start with 'ABC'</mat-error>
            </mat-form-field>
            <mat-autocomplete #auto="matAutocomplete" (optionSelected)="validateOnCharacterChange($event.option.value)">
            <mat-option *ngFor="let val of abcSuggestions | async" [value]="val">{{ val }}</mat-option>
            </mat-autocomplete>
        </div>
    <div>&nbsp;</div>
    <div>
            <mat-form-field>
                <input matInput formControlName="anyText" placeholder="Any text">
                <mat-error></mat-error>
            </mat-form-field>
    </div>
    </form>
    <span class="version-info">Current build: {{version.full}}</span>
</div>

проверьте рабочий stackblitz.

также, используя this.textInput.nativeElement.blur();, вы можете размыть каждое событие, которое хотите, а не просто щелкнуть за пределами ввода. надеюсь это поможет.

person Fateme Fazli    schedule 23.06.2018
comment
Спасибо за Ваш ответ. Но проверка каждого символа слишком дорогая. В реальном приложении проверка выполняется на стороне сервера и поэтому занимает некоторое время. Вот почему я переключился на updateOn: 'blur' в первую очередь. Я не ищу проверки чаще, но реже. - person Codo; 23.06.2018
comment
поэтому я думаю, что достаточно проверить выбранную опцию, удалить событие ввода и будет то, что вы хотите, и я думаю, что это не слишком дорого ?! - person Fateme Fazli; 23.06.2018
comment
Да, это может быть обходной путь. Немного жаль, что он проверяется дважды в пределах доли раздела и быстро мигает красным; но вроде работает. - person Codo; 23.06.2018
comment
@Codo, или вы можете размыть выбранную опцию, и автоматически будет проверена проверка. - person Fateme Fazli; 23.06.2018
comment
Я не уверен, что понимаю решение. Как это должно работать и улучшать вышеуказанный обходной путь? - person Codo; 23.06.2018
comment
@Codo проблема в том, что проверка проверяется, когда вы щелкаете по входу и размываете его, поэтому при автозаполнении, когда мы выбираем параметр, если мы не нажимаем на ввод и не размываем его, проверка не будет проверяться и по-прежнему будет иметь состояние до (действительно или недействительно), проверив проверку в методе onselect или размыв ввод в onselect, проверка будет проверена без щелчка по входу. - person Fateme Fazli; 23.06.2018
comment
Извините, объяснение слишком запутанное для меня. Я этого не понимаю. В настоящее время у меня есть исходная реализация плюс дополнительная проверка, когда запускается optionSelected компонента автозаполнения (из вашего ответа). Кажется, что это работает, но немного некрасиво, так как ошибка проверки быстро появляется и исчезает. Что вы предлагаете мне изменить дополнительно? - person Codo; 23.06.2018