JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Отображение ошибок и не только

Отображение ошибок и не только

Модуль 4: Node.js, Next.js и Angular
16 уровень , 9 лекция
Открыта

1. Отображение ошибок в формах

Пользователь редко заполняет форму идеально с первого раза — и это нормально. Хороший интерфейс всегда "подсказывает", что не так: "Пароль слишком короткий", "Почта невалидна", "Это поле обязательно". Если ошибки не показывать, пользователь будет чувствовать себя как в квесте без подсказок — и, скорее всего, сдастся.

Ошибки в Template-driven формах

В шаблонных формах Angular автоматически отслеживает состояние каждого поля: его валидность, был ли к нему доступ (touched, dirty), и какие ошибки возникли. Всё это доступно через локальную переменную, которую мы объявляем прямо в шаблоне с помощью директивы #:

<input name="email" ngModel required email #emailCtrl="ngModel">
<div *ngIf="emailCtrl.invalid && (emailCtrl.dirty || emailCtrl.touched)">
  <small *ngIf="emailCtrl.errors?.required">Поле обязательно!</small>
  <small *ngIf="emailCtrl.errors?.email">Введите корректный email!</small>
</div>

Пояснения:
emailCtrl.invalid — поле невалидно.
emailCtrl.dirty — пользователь что-то менял.
emailCtrl.touched — пользователь хотя бы раз зашёл на поле.

Обычно ошибки показывают только если пользователь уже попытался что-то ввести или ушёл с поля, чтобы не пугать его "красным" до первого клика.

Пример: форма регистрации

<form #regForm="ngForm" (ngSubmit)="onSubmit(regForm)">
  <input name="username" ngModel required minlength="3" #userCtrl="ngModel">
  <div *ngIf="userCtrl.invalid && (userCtrl.dirty || userCtrl.touched)">
    <small *ngIf="userCtrl.errors?.required">Имя обязательно</small>
    <small *ngIf="userCtrl.errors?.minlength">Минимум 3 символа</small>
  </div>

  <button type="submit" [disabled]="regForm.invalid">Зарегистрироваться</button>
</form>

Ошибки в Reactive формах

В реактивных формах всё ещё интереснее: все ошибки и состояния доступны через FormControl (или FormGroup). Обычно мы описываем форму в компоненте:

import { FormBuilder, Validators } from '@angular/forms';

form = this.fb.group({
  email: ['', [Validators.required, Validators.email]],
  password: ['', [Validators.required, Validators.minLength(6)]]
});

В шаблоне обращаемся к контролям через геттеры или напрямую:

<input formControlName="email">
<div *ngIf="form.get('email')?.invalid && (form.get('email')?.dirty || form.get('email')?.touched)">
  <small *ngIf="form.get('email')?.errors?.required">Email обязателен</small>
  <small *ngIf="form.get('email')?.errors?.email">Некорректный email</small>
</div>

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

get email() { return this.form.get('email'); }

И тогда в шаблоне:

<input formControlName="email">
<div *ngIf="email?.invalid && (email?.dirty || email?.touched)">
  <small *ngIf="email?.errors?.required">Email обязателен</small>
  <small *ngIf="email?.errors?.email">Некорректный email</small>
</div>

Пример: форма авторизации

form = this.fb.group({
  login: ['', Validators.required],
  password: ['', [Validators.required, Validators.minLength(6)]]
});
<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <input formControlName="login" placeholder="Логин">
  <div *ngIf="form.get('login')?.invalid && (form.get('login')?.dirty || form.get('login')?.touched)">
    <small *ngIf="form.get('login')?.errors?.required">Введите логин</small>
  </div>
  <input type="password" formControlName="password" placeholder="Пароль">
  <div *ngIf="form.get('password')?.invalid && (form.get('password')?.dirty || form.get('password')?.touched)">
    <small *ngIf="form.get('password')?.errors?.required">Введите пароль</small>
    <small *ngIf="form.get('password')?.errors?.minlength">Минимум 6 символов</small>
  </div>
  <button type="submit" [disabled]="form.invalid">Войти</button>
</form>

2. Обработка отправки формы

Template-driven формы

В шаблонных формах обработчик отправки обычно выглядит так:

<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
  <!-- ... -->
  <button type="submit">Отправить</button>
</form>
onSubmit(form: NgForm) {
  if (form.valid) {
    console.log('Значения формы:', form.value);
    // Тут можно отправить данные на сервер
  } else {
    // Можно подсветить ошибки
    console.log('Форма невалидна');
  }
}

Пояснение:
form.value — объект с данными всех полей.
form.valid — булево, форма валидна ли.

Reactive формы

В реактивных формах всё аналогично, но мы работаем с экземпляром FormGroup:

<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <!-- ... -->
  <button type="submit">Отправить</button>
</form>
onSubmit() {
  if (this.form.valid) {
    console.log('Отправляем:', this.form.value);
    // Отправка данных
  } else {
    // Можно выделить все поля как touched, чтобы показать ошибки
    this.form.markAllAsTouched();
  }
}

Фишка: markAllAsTouched() — подсвечивает все ошибки, даже если пользователь не заходил в поля.

3. Получение значений из формы

Template-driven

В шаблонных формах значения доступны через объект формы:

onSubmit(form: NgForm) {
  // form.value — это объект вида { email: '...', password: '...' }
  const { email, password } = form.value;
  // Используем значения
}

Reactive

В реактивных формах всё аналогично:

onSubmit() {
  const { email, password } = this.form.value;
  // Используем значения
}

Для частого доступа к отдельному полю:

const emailValue = this.form.get('email')?.value;

Получение "сырых" и "валидных" значений
В реактивных формах есть методы:
form.getRawValue() — возвращает значения всех контролов, даже если они disabled.
form.value — возвращает только enabled-поля.

4. Массовое отображение ошибок: универсальный подход

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

Пример: компонент для ошибок

// error-messages.component.ts
import { Input } from '@angular/core';
import { AbstractControl } from '@angular/forms';

@Component({
  selector: 'app-error-messages',
  template: `
    <div *ngIf="control?.invalid && (control?.dirty || control?.touched)">
      <small *ngIf="control?.errors?.required">Обязательное поле</small>
      <small *ngIf="control?.errors?.email">Некорректный email</small>
      <small *ngIf="control?.errors?.minlength">Минимум {{control?.errors?.minlength.requiredLength}} символов</small>
      <!-- Добавьте другие ошибки по необходимости -->
    </div>
  `
})
export class ErrorMessagesComponent {
  @Input() control?: AbstractControl | null;
}

Использование:

<input formControlName="email">
<app-error-messages [control]="form.get('email')"></app-error-messages>

5. Сравнение Template-driven и Reactive Forms

Характеристика Template-driven (шаблонные) Reactive (реактивные)
Где описывается логика В шаблоне (HTML) В компоненте (TypeScript)
Связывание Директивы ngModel, ngForm formControl, formGroup, formArray
Масштабируемость Хорошо для простых, маленьких форм Идеально для сложных, больших форм
Валидация В основном через атрибуты/директивы Через валидаторы, можно кастомные
Тестируемость Сложнее тестировать Легко тестировать
Динамическое создание Сложно реализовать Очень просто
Производительность Чуть медленнее на больших формах Лучше масштабируется
Типичные сценарии Регистрация, логин, простые формы Большие анкеты, динамические формы

Когда выбирать какой подход?

Template-driven: если у вас простая форма, мало логики, не нужно динамически добавлять поля, и вы не любите много TypeScript-кода.
Reactive: если форма большая, сложная, много условий, динамические поля, сложная валидация, интеграция с внешними сервисами, или вы хотите писать тесты.

Могут ли формы сочетаться?

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

6. Типичные ошибки при работе с формами

Ошибка №1: Не подключён FormsModule или ReactiveFormsModule.
Если забыть импортировать нужный модуль в app.module.ts, Angular не поймёт директивы (ngModel, formGroup и т.д.) и выдаст ошибку типа "Can't bind to 'formGroup' since it isn't a known property".

Ошибка №2: Не указан name в input для Template-driven.
Без атрибута name Angular не сможет отслеживать поле в шаблонной форме. Всё работает, только если у каждого поля есть уникальное имя!

Ошибка №3: Не подписаны на изменения формы.
В реактивных формах часто забывают подписаться на изменения (valueChanges, statusChanges), если нужно реагировать на ввод пользователя.

Ошибка №4: Показываются ошибки до первого взаимодействия.
Не стоит показывать сообщения об ошибках сразу после рендера формы — это отпугивает пользователя. Лучше показывать их только после того, как поле стало dirty или touched.

Ошибка №5: Не обновляются ошибки при динамическом изменении валидаторов.
Если вы динамически добавляете/убираете валидаторы в реактивной форме, не забудьте вызвать updateValueAndValidity() для контролов.

Ошибка №6: Смешивание Template-driven и Reactive подходов в одном компоненте.
Это приводит к путанице и неочевидным багам. Лучше придерживаться одного стиля на компонент.

Ошибка №7: Не используете markAllAsTouched() при отправке.
Если пользователь отправляет пустую форму, а вы не вызвали markAllAsTouched(), ошибки могут не появиться — пользователь не поймёт, что делать.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 16 уровень, 9 лекция
Недоступна
Простая форма обратной связи
Простая форма обратной связи
1
Задача
Модуль 4: Node.js, Next.js и Angular, 16 уровень, 9 лекция
Недоступна
Регистрация с проверкой электронной почты
Регистрация с проверкой электронной почты
1
Задача
Модуль 4: Node.js, Next.js и Angular, 16 уровень, 9 лекция
Недоступна
Заявка на экскурсию с пользовательским валидатором
Заявка на экскурсию с пользовательским валидатором
1
Задача
Модуль 4: Node.js, Next.js и Angular, 16 уровень, 9 лекция
Недоступна
Управление профилем с редактированием и валидацией
Управление профилем с редактированием и валидацией
1
Задача
Модуль 4: Node.js, Next.js и Angular, 16 уровень, 9 лекция
Недоступна
Расширенная форма заказа с динамическим списком товаров
Расширенная форма заказа с динамическим списком товаров
3
Опрос
Встроенные валидаторы, 16 уровень, 9 лекция
Недоступен
Встроенные валидаторы
Встроенные валидаторы
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ