Мое приложение Angular использует сложные проверки, выполняемые на стороне сервера. По этой причине я настроил, что обновления и проверки запускаются только по событиям размытие:
this.form = new FormGroup(
{ ... },
{
updateOn: 'blur'
}
);
Он отлично работает, за исключением полей, в которых используется автозаполнение. Если автозаполнение открыто и пользователь щелкает мышью по записи, происходит неудачная последовательность событий:
Результат выглядит так, как показано ниже (упрощенный пример). Допустимое значение находится в текстовом поле, но оно помечено как ошибка, поскольку проверка выполнялась на старом значении.
Технически правильно, что проверка была запущена, потому что щелчок всплывающего окна автозаполнения вызывает событие размытие. Однако с точки зрения пользовательского интерфейса это нонсенс. Проверка должна произойти, когда вы закончите работу с полем и перейдете к следующему полю.
Итак, как я могу предотвратить событие размытие и раннюю проверку?
Я создал простой Пример StackBlitz. Он использует аналогичную настройку, но выполняет проверку на стороне клиента (и проверяет, начинается ли текст с «ABC»). Чтобы воспроизвести проблему, введите «34», а затем выберите «ABC34» во всплывающем окне автозаполнения с помощью мыши.





для запуска при смене символа мы должны запускать событие ввода, а также событие автозаполнения изменения, поэтому вы можете попробовать что-то вроде этого:
в компоненте:
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.info(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.info(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> </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(); вы можете размыть в любом желаемом событии, а не просто щелкнуть за пределами ввода.
надеюсь это поможет.
поэтому я думаю, что достаточно проверить это на выбранной опции, удалить событие ввода и будет то, что вы хотите, и я думаю, что это не слишком дорого ?!
Да, это может быть обходной путь. Немного прискорбно, что он проверяется дважды в пределах части раздела и быстро мигает красным; но вроде работает.
@Codo, или вы можете размыть выбранную опцию, и автоматически будет проверяться проверка.
Я не уверен, что понимаю решение. Как это должно работать и улучшить описанный выше обходной путь?
@Codo проблема в том, что проверка проверяется, когда вы щелкаете вне ввода и размываете его, поэтому при автозаполнении, когда мы выбираем опцию, если мы не щелкаем вне ввода и не размываем его, проверка не будет проверяться и все равно будет иметь состояние до (действительный или недействительный), путем проверки проверки в методе onselect или размытия ввода в onselect проверка будет проверяться без щелчка на входе.
Извините, объяснение меня слишком сбивает с толку. Я этого не понимаю. В настоящее время у меня есть исходная реализация плюс дополнительная проверка при срабатывании optionSelected компонента автозаполнения (из вашего ответа). Кажется, что это работает, но немного некрасиво, так как ошибка проверки быстро появляется и исчезает. Что вы предлагаете изменить дополнительно?
Спасибо за Ваш ответ. Но проверка каждого символа обходится слишком дорого. В реальном приложении проверка происходит на стороне сервера и, следовательно, занимает некоторое время. Поэтому я в первую очередь перешел на
updateOn: 'blur'. Я ищу валидацию не чаще, а реже.