Проверка формы и понятные сообщения
Черновик уже отделён от подтверждённой заявки, но пустой или слишком длинный заголовок нельзя отправлять как обычное изменение. Добавим понятную проверку формы. Её задача двоякая: дать диспетчеру возможность исправить значение до запроса и сохранить серверу право окончательно проверить присланные данные. Результат этой главы — форма, которая связывает правило title с сообщением у поля, не превращая красную рамку в единственное объяснение проблемы.
Договор заголовка — от трёх до ста двадцати символов после удаления пробелов по краям. Здесь «символ» у клиента означает длину JavaScript-строки, а PostgreSQL char_length считает по своим текстовым правилам. Для обычных заголовков из исходного набора разницы не видно; сложные emoji и комбинируемые Unicode-последовательности требуют отдельного согласования. Лаборатория не обещает одинаковую длину пользовательских графем. Это существенная граница между удобной встроенной проверкой и полноценной международной моделью ввода.
Когда показывать ошибку
Пустая новая форма ещё не означает, что пользователь совершил ошибку. Возможно, он только открыл страницу. Поэтому текст нарушения показывается, когда control touched и invalid. Touched сообщает, что поле было посещено и потеряло фокус; invalid сообщает, что текущие значения не удовлетворяют валидаторам. Это разные признаки, и каждый нужен для своей части поведения. После попытки сохранить markAllAsTouched раскрывает ошибки даже у полей, к которым человек ещё не перешёл.
Ниже фрагмент template конечного DetailPage. Значение title и validators существуют в форме предыдущей главы. Label связан с input через for/id, пояснение — через aria-describedby. Вставляйте этот участок вместо старого поля title, а не второй копией рядом с ним: повторяющийся id нарушит связь метки и контрола. Формулировка сообщает правило заранее, тогда как сообщение об ошибке помогает понять необходимость исправления после взаимодействия.
<label for="title">Заголовок</label>
<input id="title" formControlName="title" aria-describedby="title-help">
<p id="title-help">От 3 до 120 символов после удаления пробелов по краям.</p>
@if (form.controls.title.touched && form.controls.title.invalid) {
<p role="alert">Проверьте длину заголовка.</p>
}
Ожидается, что при одном введённом символе и выходе из title появится «Проверьте длину заголовка». После ввода «Заменить проектор» правило выполнится, и сообщение исчезнет. Сам control при этом остаётся в DOM, а его label сохраняет связь. Пользователь не должен заново искать поле после каждого изменения validity. Конечная CSS-рамка role alert обозначает сообщение текстом и визуально, но не заменяет логическое состояние формы и не создаёт серверную проверку.
Нормализация перед отправкой
В save сначала записывается trimmed-значение, затем вызывается markAllAsTouched, затем проверяется form.invalid. Этот порядок предотвращает отправку строки из пробелов. Пока человек вводит текст, built-in minLength рассматривает необрезанную строку: пять пробелов имеют длину пять. После trim они становятся пустой строкой, required обнаруживает отсутствие значения, и save прекращается до HTTP. Поэтому не обещайте, что встроенный валидатор сам понимает предметную нормализацию, которой ему не задавали.
Другой возможный подход — собственный валидатор trimmedLength, который проверяет нормализованную длину при каждом изменении. Он улучшил бы соответствие подсказки поведению во время ввода, однако добавляет определение того, когда менять само значение и когда только вычислять ошибку. В архиве выбран простой вариант нормализации перед submit. При развитии формы полезно написать отдельную функцию проверки и сравнить состояния « а », пробелы и текст с пробелом внутри; внутренние пробелы удалять не требуется.
Проверка status в типизированном FormControl защищает обычный код от случайного присваивания неизвестного литерала. Но пользователь может изменить запрос вне этого интерфейса, а сервер может получить JSON из другого клиента. Nest DTO применит IsIn независимо от TS-типа Angular. Это объясняет, почему совпадающие правила существуют по обе стороны API: браузер помогает пользователю, сервер проверяет свою границу доверия. Удаление серверного правила ради отсутствия повторения было бы ошибкой архитектуры.
Ошибка формы и ошибка сохранения
Слишком короткий title — ошибка значения. Ответ 403 — отказ права, а 409 — несовпадение основы версии. Эти случаи нельзя объединять одним красным сообщением «Исправьте поля»: корректный title не решит конфликт, и увеличение его длины не создаст роль диспетчера. В конечной странице локальная ошибка находится у title, а ошибка API — отдельным сообщением над редактором. Так пользователь получает действие, которое связано с реальной причиной отказа.
Кнопка save проверяет loading и saving в дополнение к роли. Даже если DOM-кнопка disabled, обработчик повторяет эти условия. Отключённое состояние служит удобству пользователя, а метод удерживает внутренний договор операции. Например, две последовательные попытки до завершения HTTP не должны создавать два одинаковых PATCH. Это правило не относится к валидатору поля; оно принадлежит жизненному циклу сохранения и будет разобрано после подключения транспорта.
Не следует отключать submit навсегда при invalid-форме, оставляя человеку только молчаливую кнопку. Удобно позволить попытку, показать сообщения и перевести внимание к полю, если интерфейс умеет это делать доступно. В лаборатории button отключается во время асинхронных операций, а invalid останавливает save после markAllAsTouched. Автоматическое управление фокусом первого ошибочного поля не реализовано. Эту оставшуюся часть нужно проверить при самостоятельном развитии, а не объявлять полностью готовой из-за наличия aria-describedby.
Полезный сценарий для читателя: открыть rq-101, очистить title, нажать сохранение, затем ввести два символа и снова нажать. Ожидается отсутствие HTTP-запроса в обоих случаях и понятный текст у title. Затем ввод трёх обычных букв удовлетворяет минимальному правилу, но отправка ещё может получить серверный отказ. Такой переход показывает, что валидность формы разрешает попытку записи, а не подтверждает результат этой попытки и не доказывает актуальность version.
Следующий урок подключит HttpClient к тем же значениям. Предметная форма сохранит свои поля, а её доверие к ответу API будет ограничено отдельной runtime-проверкой. Мы уже определили, какие сообщения относятся к вводу и какие требуют другого решения. Благодаря этому транспорт можно добавить без скрытого изменения правил заголовка и без превращения каждого исключения в одинаковую подсказку о длине.
Статус примера — not_run. Зависимости, приложение, SQL, HTTP и тесты не запускались. Описанные ответы и изменения интерфейса ожидаемые; чтение документации не является проверкой выполнением. Полная конечная версия находится в архиве исходников, а её ограничения и действия читателя — в README и CONTRACT.md.
Перейдите к оглавлению серии или общему маршруту интерактивной веб-разработки. Механизм этой главы сверяется с официальным первичным источником; конкретные версии и непроверенные части перечислены в договоре курса.