Валидаторы Angular определяют, что является недопустимым значением, но не управляют тем, когда сообщать об ошибке пользователю. Без дополнительного механизма ошибки появлялись бы сразу при загрузке страницы, ещё до того, как пользователь начал ввод данных.
В Koobiq эта задача решается через ErrorStateMatcher.
ErrorStateMatcher — это механизм, определяющий момент отображения ошибки. Он анализирует состояние контрола (AbstractControl) и формы (FormGroupDirective | NgForm), возвращая true, если ошибку нужно показать.
Валидаторы и FormControl при этом остаются неизменными — ErrorStateMatcher не добавляет и не удаляет ошибки. Он только решает, когда kbq-form-field должен показать дочерние элементы <kbq-error> и применить CSS-класс kbq-invalid.
Иными словами: ошибки принадлежат FormControl. Вызов control.hasError('required') вернет true с момента инициализации контрола, если поле обязательно для заполнения. ErrorStateMatcher лишь определяет, когда стоит сообщить об этом пользователю.
Подключение:
Применить ErrorStateMatcher ко всем элементам управления внутри компонента через providers:
@Component({
providers: [
// Устанавливаем кастомный ErrorStateMatcher для всего компонента
kbqErrorStateMatcherProvider(ShowOnFormSubmitErrorStateMatcher)
]
})
Переопределить для конкретного элемента управления через input [errorStateMatcher] (поддерживается в kbq-tag-list, kbq-file-upload и других):
<kbq-file-upload [errorStateMatcher]="myMatcher" ... />
Один валидатор
Несколько валидаторов
Список тегов
kbq-tag-list поддерживает ErrorStateMatcher и интегрируется с kbq-form-field так же, как другие элементы управления.
Загрузка файлов
Компоненты загрузки файлов поддерживают ErrorStateMatcher, но имеют собственный layout вне kbq-form-field, поэтому вывод ошибок отличается от полей ввода. Все примеры валидации — на странице Загрузка файлов.
Используйте ShowRequiredOnSubmitErrorStateMatcher, если нужно разделённое поведение:
required, пока не отправится форма.Некоторые правила сравнивают сразу несколько полей: «подтвердите пароль», «новый пароль должен отличаться от
старого», «дата окончания не может быть раньше даты начала». Один FormControl не видит значение соседнего
поля. Поэтому для такого правила естественное место — валидатор на FormGroup, а не на одном из полей.
<kbq-error> и красная рамка зависят от одного: errorState контрола.
errorState — это просто возвращаемое значение ErrorStateMatcher.isErrorState(control, form). А control
здесь — всегда собственный FormControl поля: ErrorStateMatcher никогда не видит группу.
Поэтому валидатор может сделать группу невалидной, а проверка все равно ответит «нет». Ни одно поле не
станет красным. <kbq-error> не появится.
Это не баг. Это разделение ответственности: валидаторы решают, что невалидно, а ErrorStateMatcher решает,
когда и где это показать.
Валидатор остается на группе — его логика и форма ошибки полностью ваши. ShowOnCrossFieldErrorStateMatcher
добавляет недостающую половину: он показывает ошибку группы на тех контролах, которых она касается.
Ошибка показывается лишь тогда, когда затронуты (touched) все ее контролы, или когда отправлена форма
— иначе пара полей покраснела бы, пока пользователь еще набирает второе значение.
От вас нужно одно — функция, которая по каждой ошибке возвращает имена нужных ей контролов, либо null для
ошибки, которую она не опознаёт. Имя может указывать на контрол во вложенной группе ('passwords.confirm')
или на строку FormArray, а само правило может висеть на любом предке, а не только на непосредственном
родителе.
У правила на любом предке есть оговорка: каждый контрол пересчитывает собственный errorState только в
своём ngDoCheck. Если названные контролы лежат в разных OnPush-компонентах (каждый со своим
formGroupName), ввод в одном пометит грязной только его вью — соседний компонент с другим названным
контролом сам себя не обновит. В этом случае подпишитесь на statusChanges группы и вызовите
markForCheck() на нужных вью.
Одного экземпляра ErrorStateMatcher хватает на любое число правил на группе — в примере выше на одной группе
два правила, и оба показывает один и тот же ErrorStateMatcher.
Компаратор по умолчанию (===) не работает для дат и других объектных значений: нужен compareDate() /
compareDateTime() / sameDate() из DateAdapter либо свой компаратор.
Один случай не подходит под этот паттерн: «хотя бы одно из этих полей должно быть заполнено». Для такого правила поля не помечают невалидными, а показывают общий алерт над формой. См. руководство по валидации и его пример «Обязательные поля на выбор».
Правило, которое может проверить только сервер, вешается на группу как AsyncValidatorFn. Ответ приходит вне
какого-либо события на вью самого поля — не клик, не нажатие клавиши, ничего, на что OnPush-вью обычно
реагирует, — поэтому она не перепроверяет себя сама, когда результат приходит, независимо от того, ложится
ли ошибка на группу или на дочерний контрол. Нужен сигнал из statusChanges группы.