Skip to content

Template-driven Forms

Template-driven формы создаются декларативно в шаблоне с помощью директив. Angular автоматически генерирует модель формы на основе HTML.

Подключение

typescript
import { FormsModule } from '@angular/forms';

@NgModule({
  imports: [FormsModule]
})
export class AppModule {}

1. Базовая форма

typescript
@Component({
  selector: 'app-login',
  template: `
    <form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)">
      <div>
        <label>Email:</label>
        <input name="email"
               [(ngModel)]="user.email"
               required
               email
               #emailField="ngModel">
        <div *ngIf="emailField.invalid && emailField.touched">
          <small *ngIf="emailField.errors?.['required']">Email обязателен</small>
          <small *ngIf="emailField.errors?.['email']">Неверный формат email</small>
        </div>
      </div>

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

      <button type="submit" [disabled]="loginForm.invalid">Войти</button>
    </form>

    <pre>Форма валидна: {{ loginForm.valid }}</pre>
    <pre>{{ loginForm.value | json }}</pre>
  `
})
export class LoginComponent {
  user = {
    email: '',
    password: ''
  };

  onSubmit(form: NgForm) {
    if (form.valid) {
      console.log('Данные формы:', form.value);
    }
  }
}

2. ngForm и ngModel

ngForm

Angular автоматически создаёт директиву ngForm для каждого тега <form>. Она предоставляет:

html
<form #myForm="ngForm">
  <!-- myForm.valid — форма валидна -->
  <!-- myForm.invalid — есть ошибки -->
  <!-- myForm.value — объект со всеми значениями -->
  <!-- myForm.dirty — данные менялись -->
  <!-- myForm.submitted — форма отправлена -->
</form>

ngModel

Каждый элемент с ngModel и атрибутом name автоматически регистрируется в форме:

html
<form #f="ngForm">
  <!-- Обязательно указать name -->
  <input name="firstName" [(ngModel)]="model.firstName">
  <input name="lastName" [(ngModel)]="model.lastName">

  <!-- Доступ к значениям -->
  <pre>{{ f.value | json }}</pre>
  <!-- { "firstName": "...", "lastName": "..." } -->
</form>

Три режима ngModel

html
<!-- 1. Двусторонняя привязка -->
<input [(ngModel)]="name" name="name">

<!-- 2. Одностороняя привязка (только чтение из модели) -->
<input [ngModel]="name" name="name">

<!-- 3. Без привязки (значение только в форме) -->
<input ngModel name="name">

3. Группировка полей (ngModelGroup)

html
<form #f="ngForm" (ngSubmit)="onSubmit(f)">
  <div ngModelGroup="personalInfo" #personalGroup="ngModelGroup">
    <h3>Личная информация</h3>
    <input name="firstName" [(ngModel)]="model.firstName" required>
    <input name="lastName" [(ngModel)]="model.lastName" required>
    <p *ngIf="personalGroup.invalid">Заполните все поля</p>
  </div>

  <div ngModelGroup="address">
    <h3>Адрес</h3>
    <input name="city" [(ngModel)]="model.city">
    <input name="street" [(ngModel)]="model.street">
  </div>

  <button type="submit">Сохранить</button>
</form>

<!-- Результат f.value:
{
  personalInfo: { firstName: '', lastName: '' },
  address: { city: '', street: '' }
}
-->

4. Элементы формы

Select

html
<select name="role" [(ngModel)]="user.role" required>
  <option value="">-- Выберите роль --</option>
  <option *ngFor="let role of roles" [value]="role.id">
    {{ role.name }}
  </option>
</select>
typescript
roles = [
  { id: 'admin', name: 'Администратор' },
  { id: 'user', name: 'Пользователь' },
  { id: 'guest', name: 'Гость' }
];

Select с объектами (ngValue)

html
<select name="city" [(ngModel)]="selectedCity">
  <option *ngFor="let city of cities" [ngValue]="city">
    {{ city.name }}
  </option>
</select>

<!-- selectedCity будет объектом { id: 1, name: 'Москва' }, а не строкой -->

Checkbox

html
<label>
  <input type="checkbox" name="agree" [(ngModel)]="user.agree" required>
  Я согласен с условиями
</label>

Radio

html
<label>
  <input type="radio" name="gender" [(ngModel)]="user.gender" value="male">
  Мужской
</label>
<label>
  <input type="radio" name="gender" [(ngModel)]="user.gender" value="female">
  Женский
</label>

Textarea

html
<textarea name="bio"
          [(ngModel)]="user.bio"
          maxlength="500"
          rows="4">
</textarea>
<small>{{ user.bio.length }}/500</small>

5. CSS-классы состояния

Angular автоматически добавляет CSS-классы к элементам формы:

Состояниеtruefalse
Посещёнng-touchedng-untouched
Изменёнng-dirtyng-pristine
Валиденng-validng-invalid
css
input.ng-invalid.ng-touched {
  border: 2px solid red;
}

input.ng-valid.ng-touched {
  border: 2px solid green;
}

6. Сброс формы

html
<form #f="ngForm" (ngSubmit)="onSubmit(f)">
  <!-- поля формы -->
  <button type="submit">Отправить</button>
  <button type="button" (click)="f.resetForm()">Сбросить</button>
</form>
typescript
onSubmit(form: NgForm) {
  if (form.valid) {
    console.log(form.value);
    // Сброс формы после отправки
    form.resetForm();
  }
}

Reactive Forms vs Template-driven Forms

КритерийReactive FormsTemplate-driven Forms
Модель формыВ классе компонентаВ шаблоне
ИмпортReactiveFormsModuleFormsModule
Привязка данныхЯвная, синхроннаяНеявная через ngModel
ВалидацияФункции в классеДирективы в шаблоне
ТестированиеПростое (без DOM)Требует рендеринга
Динамические поляЛегко (FormArray)Сложно
Сложные формыРекомендуетсяНе рекомендуется
Простые формыИзбыточноИдеально