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

Отображение ошибок валидации и обработка отправки формы в Angular

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

1. Отображение ошибок валидации

Формы — это мост между пользователем и вашим приложением. Если пользователь не понимает, почему форма не отправляется, или видит сухое "Ошибка", он скорее всего отправится искать другое приложение. Поэтому важно не только валидировать данные, но и показывать человеку, что именно не так, и как это исправить.

В Angular для этого есть всё необходимое: у каждого поля формы есть статус, ошибки, и удобные способы отобразить их в шаблоне. Начнём с самого простого — Template-driven Forms.

Как узнать, что поле невалидно

Каждое поле, связанное с ngModel, автоматически становится частью формы. Angular создаёт для него объект типа NgModel, который содержит свойства:

  • valid / invalid — валидно ли поле
  • errors — объект с ошибками валидации (например, { required: true })
  • touched — поле было в фокусе и покинуто
  • dirty — значение поля менялось

Пример: форма с ошибками


<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
  <label>
    Email:
    <input
      name="email"
      ngModel
      required
      email
      #emailCtrl="ngModel"
      />
  </label>
  <div *ngIf="emailCtrl.invalid && (emailCtrl.dirty || emailCtrl.touched)">
    <small *ngIf="emailCtrl.errors?.required" style="color: red;">
      Email обязателен
    </small>
    <small *ngIf="emailCtrl.errors?.email" style="color: red;">
      Введите корректный email
    </small>
  </div>

  <label>
    Пароль:
    <input
      name="password"
      ngModel
      required
      minlength="6"
      #passCtrl="ngModel"
      type="password"
      />
  </label>
  <div *ngIf="passCtrl.invalid && (passCtrl.dirty || passCtrl.touched)">
    <small *ngIf="passCtrl.errors?.required" style="color: red;">
      Пароль обязателен
    </small>
    <small *ngIf="passCtrl.errors?.minlength" style="color: red;">
      Минимум {{ passCtrl.errors?.minlength.requiredLength }} символов
    </small>
  </div>

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

Как это работает?

  • Мы задаём локальные переменные #emailCtrl="ngModel" и #passCtrl="ngModel", чтобы обращаться к их статусу.
  • Блок с ошибками появляется только если поле невалидно и пользователь уже пытался его заполнить (dirty/touched).
  • Для каждой ошибки выводим своё сообщение.

Типичные паттерны

  • Всегда показывайте ошибки после того, как пользователь попытался что-то ввести, иначе форма будет пугать его красными ошибками с самого начала.
  • Используйте свойства dirty (значение менялось) или touched (потеряно фокус), чтобы контролировать момент появления ошибок.

2. Отображение ошибок: Reactive Forms

Reactive Forms в Angular дают больше контроля и возможностей, но принцип тот же.

Пример формы

В компоненте:


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

@Component({
  selector: 'app-register-form',
  templateUrl: './register-form.component.html'
})
export class RegisterFormComponent {
  form = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(6)]],
  });

  constructor(private fb: FormBuilder) {}

  onSubmit() {
    if (this.form.valid) {
      // Отправляем данные
      console.log('Форма отправлена:', this.form.value);
    } else {
      // Помечаем все поля как touched, чтобы сразу показать ошибки
      this.form.markAllAsTouched();
    }
  }
}

В шаблоне:


<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <label>
    Email:
    <input formControlName="email" />
  </label>
  <div *ngIf="form.get('email')?.invalid && (form.get('email')?.dirty || form.get('email')?.touched)">
    <small *ngIf="form.get('email')?.errors?.required" style="color: red;">
      Email обязателен
    </small>
    <small *ngIf="form.get('email')?.errors?.email" style="color: red;">
      Некорректный email
    </small>
  </div>

  <label>
    Пароль:
    <input formControlName="password" type="password" />
  </label>
  <div *ngIf="form.get('password')?.invalid && (form.get('password')?.dirty || form.get('password')?.touched)">
    <small *ngIf="form.get('password')?.errors?.required" style="color: red;">
      Пароль обязателен
    </small>
    <small *ngIf="form.get('password')?.errors?.minlength" style="color: red;">
      Минимум {{ form.get('password')?.errors?.minlength.requiredLength }} символов
    </small>
  </div>

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

Коротко о механике

  • Для каждого поля обращаемся к контролу через form.get('имяПоля').
  • Проверяем invalid, а также dirty или touched, чтобы не показывать ошибки сразу.
  • Для каждой ошибки выводим своё сообщение.

3. Отправка формы: обработка и UX

Как обрабатывать отправку

  • В Template-driven формах используйте (ngSubmit)="onSubmit(form)".
  • В Reactive Forms — (ngSubmit)="onSubmit()" (форма доступна через поле класса).

В обработчике:

  1. Проверяйте валидность формы.
  2. Если невалидна — помечайте все поля как touched, чтобы ошибки сразу показались.
  3. Если валидна — отправляйте данные (например, через HTTP-запрос).

Пример для Template-driven:


onSubmit(form: NgForm) {
  if (form.valid) {
    // Отправляем данные
    console.log('Данные формы:', form.value);
  } else {
    // Пометить все поля как touched
    Object.values(form.controls).forEach(control => control.markAsTouched());
  }
}

Пример для Reactive Forms (см. выше):


onSubmit() {
  if (this.form.valid) {
    // Отправляем данные
    console.log('Форма отправлена:', this.form.value);
  } else {
    this.form.markAllAsTouched();
  }
}

Блокировка кнопки отправки

Кнопку "Отправить" лучше делать неактивной, если форма невалидна:


<button type="submit" [disabled]="form.invalid">Отправить</button>

Это не отменяет валидацию на стороне клиента и сервера! Никогда не доверяйте только фронтенду — пользователь может подделать форму.

4. Практика: форма регистрации с отображением ошибок

Давайте соберём всё вместе. Пример для Reactive Forms:

register-form.component.ts


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

@Component({
  selector: 'app-register-form',
  templateUrl: './register-form.component.html'
})
export class RegisterFormComponent {
  form = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(6)]],
    agree: [false, Validators.requiredTrue]
  });

  submitted = false;

  constructor(private fb: FormBuilder) {}

  onSubmit() {
    this.submitted = true;
    if (this.form.valid) {
      // Здесь обычно отправляется HTTP-запрос
      alert('Регистрация успешна!');
      this.form.reset();
      this.submitted = false;
    } else {
      this.form.markAllAsTouched();
    }
  }
}

register-form.component.html


<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <label>
    Email:
    <input formControlName="email" />
  </label>
  <div *ngIf="form.get('email')?.invalid && (form.get('email')?.dirty || form.get('email')?.touched || submitted)">
    <small *ngIf="form.get('email')?.errors?.required" style="color: red;">
      Email обязателен
    </small>
    <small *ngIf="form.get('email')?.errors?.email" style="color: red;">
      Некорректный email
    </small>
  </div>

  <label>
    Пароль:
    <input formControlName="password" type="password" />
  </label>
  <div *ngIf="form.get('password')?.invalid && (form.get('password')?.dirty || form.get('password')?.touched || submitted)">
    <small *ngIf="form.get('password')?.errors?.required" style="color: red;">
      Пароль обязателен
    </small>
    <small *ngIf="form.get('password')?.errors?.minlength" style="color: red;">
      Минимум {{ form.get('password')?.errors?.minlength.requiredLength }} символов
    </small>
  </div>

  <label>
    <input type="checkbox" formControlName="agree" />
    Согласен с правилами
  </label>
  <div *ngIf="form.get('agree')?.invalid && (form.get('agree')?.dirty || form.get('agree')?.touched || submitted)">
    <small style="color: red;">Необходимо согласиться с правилами</small>
  </div>

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

5. Как сделать универсальный компонент для ошибок

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

validation-errors.component.ts


import { Component, Input } from '@angular/core';
import { AbstractControl } from '@angular/forms';

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

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


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

6. Особенности и нюансы

  • Не показывайте ошибки сразу — это раздражает пользователя. Показывайте их после попытки ввода или отправки формы.
  • Не забывайте про серверную валидацию. Даже если на клиенте всё идеально, на сервере могут быть свои правила (например, email уже зарегистрирован). Серверные ошибки обычно отображают отдельным сообщением после попытки отправки.
  • Если форма большая, используйте markAllAsTouched() — это позволит показать ошибки сразу для всех полей, если пользователь нажал "отправить" с пустой формой.
  • В Template-driven формах нет метода markAllAsTouched(), нужно вручную пройтись по всем контролам.

7. Типичные ошибки при отображении ошибок и обработке отправки формы

Ошибка №1: ошибки появляются сразу при открытии формы.
Это происходит, если не проверять dirty или touched у поля. Пользователь пугается красных сообщений ещё до первого ввода. Решение: показывать ошибки только если поле уже изменяли или покидали.

Ошибка №2: обработчик отправки не помечает поля как touched.
Пользователь нажимает "Отправить" — форма не отправляется, но ошибок не видно. Решение: при невалидной форме вызывать markAllAsTouched() (Reactive Forms) или вручную пройтись по контролам (Template-driven).

Ошибка №3: блокировка кнопки "Отправить" только по валидности.
Если просто сделать [disabled]="form.invalid", но не показывать ошибки — пользователь не поймёт, что не так. Решение: всегда показывать причину блокировки.

Ошибка №4: отсутствие серверной валидации.
Пользователь может обойти фронтенд-валидацию, поэтому всегда проверяйте данные и на сервере, и отображайте ошибки от сервера в форме.

Ошибка №5: неочевидные сообщения об ошибках.
"Ошибка в поле" — это неинформативно. Лучше: "Email обязателен", "Пароль слишком короткий" и т.д.

Ошибка №6: забыли сбросить форму после успешной отправки.
Пользователь радуется, но форма осталась заполненной. Решение: после успешной отправки вызывайте form.reset().

3
Опрос
Формы в Angular, 16 уровень, 4 лекция
Недоступен
Формы в Angular
Формы в Angular
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ