Как веб-разработчики, большая часть нашей работы заключается в сборе информации и данных от наших пользователей. В идеальном мире мы могли бы позволить пользователям вводить данные как угодно и каким-то образом анализировать или извлекать полезную и актуальную информацию из этих данных, но на самом деле это не всегда так. Часто данные, вводимые пользователями, являются сложными и требуют, чтобы мы убедились, что они соответствуют какой-либо спецификации.

Последние пару лет я довольно много работал с проверкой ввода данных пользователем в приложениях Angular. Для этого я использовал библиотеку валидации Joi в различных ее версиях. Joi - мощная библиотека, и, поскольку это javascript, ее можно запускать как во внешнем, так и в серверном интерфейсе. Это большое преимущество! Если вы не используете серверную часть javascript, она по-прежнему является отличным инструментом для проверки внешнего интерфейса.

Если вы используете формы на основе шаблонов в Angular, вы можете использовать валидацию Joi, например, для запуска ngModel через механизм валидации Joi после того, как форма была отправлена, а затем для информирования пользователя, если введенные данные не прошли валидацию. Но это ухудшает пользовательский опыт. А подключение событий для мониторинга пользовательского ввода, запуск ввода через проверку, добавление и удаление классов для выделения ошибок - утомительная работа. Однако с использованием реактивных форм, в которые встроена поддержка проверки, это становится намного проще.

Исследуя, как использовать реактивные формы вместе с Joi, я обнаружил, что эта статья мне очень помогла: Расширенная проверка с помощью Angular и Joi. Но сейчас ему несколько лет, и в нем не рассматривается, как проверить всю форму с помощью одной схемы Joi. Это то, что я хочу показать в этой статье.

Что вы узнаете

  • Создание многоразовых спецификаций для пользовательского ввода
  • Использование спецификации для обеспечения того, чтобы форма содержала правильные данные перед отправкой
  • Визуализация ошибок для пользователя
  • Настройка сообщений об ошибках
  • Проверка перекрестных полей: если одно поле имеет значение X, другое поле должно быть Y
  • Проверка форм с переменным количеством входов (динамические формы)

Чему ты не научишься

  • Создание великолепных форм
  • Перевод сообщений об ошибках
  • Управление различиями в отображении форм в браузерах

Предпосылки

Настройка здесь основана на angular v9.1.4 и node v12.16.3 (LTS), которые являются последними на момент написания. Чтобы использовать эту статью, вам потребуется уметь использовать команды npm, node и ng в каком-либо терминале. Кроме того, я тестировал CSS только в Chrome, и он может выглядеть иначе в другом браузере.

Обратите внимание на изображения в этой статье

Для визуализации проверки я использую анимированные изображения в формате gif. Я обратил внимание на то, что иногда эти изображения загружаются некорректно, вместо этого они отображаются как нечеткие капли. В таком случае попробуйте щелкнуть изображение правой кнопкой мыши и открыть его на отдельной вкладке. Я видел это и в других статьях на Medium.

Базовая настройка

Создайте приложение Angular следующим образом:

ng new joi-validation
[some work takes place]
cd joi-validation

Теперь импортируйте ReactiveFormsModule в свой app.module (или другой модуль, если вы опытный пользователь и вам нужно это сделать).

Затем пришло время создать форму, которую мы хотим проверить. Давайте создадим форму «сообщить об инциденте», в которой пользователь может сообщить об инциденте на строительной площадке или аналогичном. Мы можем создать форму прямо в app.component.ts для простоты. Чтобы свести к минимуму код для написания, мы будем использовать службу FormBuilder для создания FormGroup и ее элементов управления. Я рекомендую этот подход.

Вот суть всех трех файлов, очевидно, вы не поместили бы весь код в один файл в реальном проекте.

Добавление дзёи

Сейчас в форме нет подтверждения. Давай изменим это.

npm install @hapi/joi -s

Теперь мы установили Joi и готовы использовать его для создания схемы, которую мы будем использовать для проверки ввода в форме.

В реальном проекте более вероятно, что схемы, которые мы используем, находятся в отдельном репозитории или пакете и используются внутри команды или организации. Но в этом случае мы просто создадим схему в app.component.

Хотя мы не использовали схему для проверки формы, мы можем протестировать схему следующим образом:

Когда вы начнете редактировать форму, вы увидите, что консоль заполняется сообщениями об ошибках:

Вы также можете видеть, что проверка возвращается раньше: как только значение не проходит проверку, проверка останавливается и возвращает первую ошибку. Это поведение по умолчанию в Joi, но его можно изменить при проверке путем передачи объекта параметров в функцию проверки:

schema.validate(values, { abortEarly: false })

Подключение схемы к форме

Пока форма все еще «действительна», поскольку мы не связали форму с нашей схемой. Чтобы это сработало, нам нужно создать функцию-валидатор, которая сможет принимать наши элементы управления формы и запускать их через проверку, а затем сообщать о любых возникших ошибках.

Заводская функция

Как упоминается во многих других статьях на эту тему - и в самой документации Angular - для проверки FormGroup (на самом деле всей формы) вы можете добавить один или несколько ValidatorFn: s в группу форм. Обычно этот метод используется для проверки формы с зависимостями перекрестного контроля / перекрестной проверки полей. Например, перекрестная проверка может выглядеть следующим образом: «Если имя« Джейк », возраст не может быть« 104 »» или что-то в этом роде. Но в этом случае мы используем эту функцию, чтобы использовать нашу схему для проверки всей формы. Схема, конечно, может иметь зависимости проверки между полями. (Подробнее об этом позже.)

Поскольку библиотека Joi не предоставляет нам Angular ValidatorFn: s, нам нужно будет создать их самостоятельно. Этого можно достичь, написав функцию, которая принимает схему Joi и возвращает функцию, которая принимает FormGroup в качестве входных данных (фабричная функция). Чтобы уменьшить сложность, я выбрал таргетинг только на FormGroup, а не на AbstractControl, от которого он наследуется.

Чтобы использовать фабричную функцию, мы вызываем ее при создании нашей формы, например:

Теперь функция валидатора будет запускаться, когда Angular обнаружит изменения в форме (для этого есть варианты, такие как 'blur' или 'change'). Функция найдет любые значения, не соответствующие критериям в схеме, и соберет возникшие ошибки в errorObj, который является просто контейнером значений ключа. Функция также (и это важно) установит ошибку для любого элемента управления, который содержит значение, не соответствующее критериям. Если бы функция этого не сделала, форма была бы недействительной (в случае, если значения не передаются), но соответствующий элемент управления все равно будет «действительным», поскольку Angular не пытается сопоставить ошибки во всей форме с соответствующими контроль. Это сделано намеренно. Опять же, мы действительно используем функцию проверки перекрестных полей для использования нашей схемы во всей форме, что, вероятно, не было основным вариантом использования для дизайнеров FormBuilder.

Замечание о функции setError

«Орлиные глаза» могли определить, что когда поле в форме, в которой ранее была ошибка, не генерирует никаких ошибок проверки, мы не удаляем ошибку в этом поле. Тогда можно было подумать, что если в каком-либо поле будет ошибка, она всегда будет там. Но это не тот случай. Причина в том, что мы не прикрепляем никаких функций проверки к каждому полю в форме. Это означает, что при каждом запуске проверки каждое измененное поле будет помечено как «действительное», и при запуске валидатора formGroup (нашего настраиваемого валидатора) он будет устанавливать ошибки в любом поле, которое не проходит валидацию. Однако в этом подходе есть одна оговорка, с которой нам нужно будет справиться позже.

Визуализация ошибок

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

Класс 'ng-invalid' становится 'ng-valid', когда количество знаков в поле Заголовок превышает пороговое значение. Как видите, элемент form по-прежнему имеет класс 'ng-invalid' после того, как поле Title стало действительным, поскольку форма содержит другие ошибки.

Как вы хотите визуализировать эти ошибки для своих пользователей, полностью зависит от вас. С помощью этих классов CSS вы можете выделить недопустимое поле и показать сообщение об ошибке. Вот пример:

Что могло бы выглядеть, например, так:

Резюме

Пока у нас есть:

  • создали многоразовую спецификацию, которой должна соответствовать наша форма
  • использовал спецификацию для проверки всей формы
  • выделил поля, содержащие ошибки
  • объяснил, в чем проблема (вроде)

Что мы еще не сделали:

  • использовал понятный для людей язык, чтобы объяснить, какие ошибки произошли
  • перекрестная проверка
  • проверка динамических форм

Быть человеком

Обычно мы стремимся быть максимально дружелюбными к людям при общении с нашими пользователями. В приведенной выше визуализации некоторые сообщения об ошибках в порядке, но последнее, siteId, требует знания RegExp для понимания. Это не здорово. К счастью, Джой может это исправить, позволив нам напрямую установить сообщение об ошибке. Это работает нормально, учитывая, что сообщение не требует объяснения контекста и написано на английском языке.

У каждого типа Joi есть необязательная message('My message') функция. Устанавливая это, вы отменяете сообщение по умолчанию, которое будет возвращено, если значение не пройдет проверку:

incidentSchema = Joi.object({
    type: Joi.any().valid('Lethal', 'Major', 'Minor'),
    title: Joi.string().required().min(5).max(32),
    description: Joi.string().required().min(20),
    // Add a human friendly message to display if
    // the Site ID does not pass validation
    //
    siteId: Joi.string().pattern(/^[a-zA-Z]{2}[0-9]{1,3}$/).message('Site ID must contain two letters and one to three digits. Example "AB123"')
  });

На момент написания этой статьи у Joi не было собственного способа перевода сообщений об ошибках. Если вам нужна поддержка неанглийских языков или многоязычное приложение, я предлагаю взглянуть на эту статью. В проекте Angular, вероятно, лучше всего использовать встроенный TranslateService и создать что-то, что может принимать объекты ошибок из Joi и преобразовывать их в ключ перевода, как показано в статье. Однако статья не поддерживает динамические сообщения об ошибках, такие как заголовок должен содержать как минимум [динамическое значение] символов. Это определенно может поддерживаться TranslateService, но потребует некоторой работы. Возможно, это тема для другой статьи.

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

Проверка между полями

Для этой статьи нам понадобится сценарий кросс-полевой проверки. Давайте решим, что для сайта с идентификатором «AB123» можно сообщать только о серьезных инцидентах. Итак, как мы можем добиться этого?

Для этого мы будем использовать функцию Joi when(). when() принимает условие и параметры, которые вместе образуют валидацию если это, то сделай это, иначе это другое (научный термин 🙄). Следует отметить, что этот тип проверки имеет некоторое влияние на производительность и может выполняться только для типа any(). Документацию можно найти здесь.

Мы изменим схему и внесем некоторые другие связанные изменения, чтобы в поле Тип отображалась ошибка, если идентификатор сайта - «AB123», а тип - не «Основной». Мы также сделаем необязательным отображение ошибок в «чистых» полях, поскольку поле Тип может быть изменено или не изменено пользователем.

Что приводит к следующему:

Как видите, когда идентификатор сайта - «AB123», тип должен быть «Major».

Ошибки застревают! 💥

Как упоминалось ранее, у нас не было необходимости удалять какие-либо ошибки, которые мы устанавливали для каждого поля в нашей функции проверки. Этот подход отлично работает до тех пор, пока у нас нет кросс-полевой проверки. Это работает, потому что, когда мы меняем содержимое поля, Angular удаляет все ошибки в этом поле, а затем выполняет проверки. Но когда мы устанавливаем ошибку в поле, которое мы не редактируем, ошибка будет в этом поле до тех пор, пока мы не удалим его или поле не изменится. Это приводит к ошибкам в нашей форме. Гифка, иллюстрирующая проблему:

Чтобы исправить это, мы можем использовать ValidatorFn, чтобы перебрать все элементы управления и удалить ошибки из тех, у которых есть такие ошибки:

const validator: ValidatorFn = (group: FormGroup) => {
      // Remove error from controls
      for (const key in group.controls) {
        const control = group.get(key);
        if (control.errors) {
          control.setErrors(null);
        }
      }
      ... rest of validator function
}

Наш ValidatorFn теперь становится немного большим, и может быть хорошей идеей разделить его на более мелкие функции. Мы могли бы, например, выделить цикл «удаления ошибок» в отдельную функцию и вызвать его, составив наш ValidatorFn из более мелких частей. Но чтобы статья не стала слишком большой, мы пока оставим ее как есть.

Динамические формы

Иногда нам нужно позволить пользователю добавлять поля в существующую форму. В этом примере мы позволим репортеру добавить поля для сообщения имен лиц, пострадавших в результате инцидента. Мы добавим кнопку для добавления полей, а затем проверим, что содержимое добавленных полей соответствует спецификации. Как собственно добавить поля в форму описано в документации Angular.

Проверка значений формы оказалась довольно простой. Мы будем использовать тип Joi array(), чтобы позволить Joi проверить правильность ряда значений. ValidatorFn уже имеет все необходимое, чтобы находить поля с ошибками и отмечать их. Нам просто нужно добавить контейнер, чтобы показывать пользователю сообщение об ошибке.

Мы также добавили в форму некоторые значения по умолчанию, чтобы уменьшить необходимость ввода текста в примере.

Чтобы сделать сообщение об ошибке более понятным для человека, мы использовали функцию messages() в Joi, чтобы добавить несколько настраиваемых сообщений об ошибках в схему. Я не уверен, что это способ управления более сложными сценариями, но это вариант, если вы не работаете с многоязычным приложением.

Последние мысли

Я надеюсь, что эта статья поможет вам повысить стабильность и удобство поддержки проверки пользовательского ввода. Очень полезно иметь возможность использовать одну и ту же проверку во внешней и задней части веб-приложения. Если вы не используете серверную часть javascript, вы все равно можете улучшить ремонтопригодность вашей проверки и, следовательно, ваших форм, создав схемы для пользовательского ввода (или других типов ввода). Схемы можно (нужно?) Тестировать и использовать повторно. Извлечение фабрики ValidatorFn в общую папку может помочь группе использовать те же методы для проверки и повышения надежности проверки.

Всегда помните о пользовательском опыте

Хотя эти инструменты могут помочь нам создавать расширенные формы, очень важно помнить об удобстве использования. Часто мы можем избавиться от необходимости в расширенной проверке и сложных формах, сделав шаг назад и спросив себя и наших пользователей: как мы можем сделать это настолько простым и безболезненным, насколько это возможно? Часто легко добавить «еще одно поле», что в конечном итоге создает впечатление пользователя, которое может расстраивать, особенно для пользователей смартфонов и планшетов.

Эта проблема «еще одно поле» является причиной того, что я решил не включать в статью формы с вложенными элементами управления. Определенно можно создавать и проверять формы с полями, которые являются FormGroups, но действительно ли это хорошая идея?

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

Воспользуйтесь помощью UX-дизайнера, или вам ничего не будет доступно, черпайте вдохновение из множества хороших примеров в Интернете.

Обратная связь

Хотя я, безусловно, приложил все усилия, чтобы написать полезную статью, в ней могут быть неясные или, что еще хуже, неправильные части. Не стесняйтесь комментировать ниже, если у вас есть какие-либо вопросы, исправления или предложения.

Исходный код

Вот ссылка на stackblitz приложения в его окончательном состоянии https://stackblitz.com/edit/angular-ivy-r4rwpw. Ссылка на репо на GitHub: https://github.com/herrklaseen/joi-validation-examples.

Спасибо за прочтение!