Внести вклад
Подробные руководства
Формы

Создание template-driven формы

В этом руководстве показано, как создать template-driven форму. Элементы управления формы привязаны к свойствам данных с валидацией ввода. Валидация помогает сохранять целостность данных, а стилизация — улучшать пользовательский опыт.

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

Template vs Reactive forms

Angular поддерживает два подхода к интерактивным формам. Template-driven формы позволяют использовать специфичные для форм директивы в шаблоне Angular. Reactive forms дают model-driven подход к построению форм.

Template-driven формы хорошо подходят для небольших или простых форм, а reactive forms более масштабируемы и удобны для сложных форм. Сравнение двух подходов см. в Выбор подхода

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

Цели

Это руководство учит, как:

  • Создать форму Angular с компонентом и шаблоном
  • Использовать ngModel для двусторонней привязки данных для чтения и записи значений input-контролов
  • Давать визуальную обратную связь с помощью специальных CSS-классов, отслеживающих состояние контролов
  • Показывать пользователям ошибки валидации и условно разрешать ввод из контролов формы на основе статуса формы
  • Делиться информацией между HTML-элементами через template reference variables

Создание template-driven формы

Template-driven формы опираются на директивы, определённые в FormsModule.

Директивы Описание
NgModel Согласует изменения значения привязанного элемента формы с изменениями модели данных, позволяя реагировать на ввод пользователя валидацией и обработкой ошибок.
NgForm Создаёт экземпляр FormGroup верхнего уровня и привязывает его к элементу <form>, чтобы отслеживать агрегированное значение формы и статус валидации. Как только вы импортируете FormsModule, эта директива по умолчанию активна на всех тегах <form>. Специальный селектор добавлять не нужно.
NgModelGroup Создаёт и привязывает экземпляр FormGroup к DOM-элементу.

Обзор шагов

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

  1. Создайте базовую форму.
    • Определите пример модели данных
    • Подключите необходимую инфраструктуру, например FormsModule
  2. Привяжите контролы формы к свойствам данных через директиву ngModel и синтаксис двусторонней привязки.
    • Изучите, как ngModel сообщает о состояниях контрола через CSS-классы
    • Задайте имена контролам, чтобы они были доступны ngModel
  3. Отслеживайте валидность ввода и статус контрола через ngModel.
    • Добавьте пользовательский CSS для визуальной обратной связи о статусе
    • Показывайте и скрывайте сообщения об ошибках валидации
  4. Реагируйте на нативное событие клика HTML-кнопки, добавляя данные в модель.
  5. Обработайте отправку формы через output-свойство ngSubmit формы.
    • Отключайте кнопку Submit, пока форма невалидна
    • После отправки замените заполненную форму другим содержимым на странице

Создание формы

  1. В предоставленном примере приложения создаётся класс Actor, который определяет модель данных, отражённую в форме.

    actor.ts

    export class Actor {
      constructor(
        public id: number,
        public name: string,
        public skill: string,
        public studio?: string,
      ) {}
    }
    
  2. Разметка и детали формы определены в классе ActorFormComponent.

    actor-form.component.ts (v1)

    import {Component} from '@angular/core';
    
    import {Actor} from '../actor';
    import {FormsModule} from '@angular/forms';
    import {JsonPipe} from '@angular/common';
    
    @Component({
      selector: 'app-actor-form',
      templateUrl: './actor-form.component.html',
      imports: [FormsModule, JsonPipe],
    })
    export class ActorFormComponent {
      skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];
    
      model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');
    
      submitted = false;
    
      onSubmit() {
        this.submitted = true;
      }
    
    }

    Значение selector компонента "app-actor-form" означает, что форму можно вставить в родительский шаблон тегом <app-actor-form>.

  3. Следующий код создаёт новый экземпляр актёра, чтобы начальная форма могла показать пример актёра.

    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');
        console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"

    В этой демонстрации для model и skills используются фиктивные данные. В реальном приложении вы бы внедрили сервис данных для получения и сохранения реальных данных или открыли эти свойства как inputs и outputs.

  4. Компонент включает возможность Forms, импортируя модуль FormsModule.

    @Component({
      selector: 'app-actor-form',
      templateUrl: './actor-form.component.html',
      imports: [FormsModule, JsonPipe],
    })
    export class ActorFormComponent {
  5. Форма отображается в макете приложения, определённом шаблоном корневого компонента.

    app.component.html

    <app-actor-form />
    

    Начальный шаблон задаёт макет формы с двумя группами полей и кнопкой отправки. Группы полей соответствуют двум свойствам модели данных Actor: name и studio. У каждой группы есть метка и поле для ввода пользователя.

    • У <input> Name есть HTML5-атрибут required
    • У <input> Studio его нет, потому что studio необязателен

    У кнопки Submit есть классы для стилизации. На этом этапе макет формы — обычный HTML5 без привязок и директив.

  6. Пример формы использует несколько классов стилей из Twitter Bootstrap: container, form-group, form-control и btn. Чтобы использовать эти стили, таблица стилей приложения импортирует библиотеку.

    styles.css

    @import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
  7. Форма требует, чтобы навык актёра выбирался из предопределённого списка skills, хранящегося внутри ActorFormComponent. Цикл Angular @for перебирает значения данных, чтобы заполнить элемент <select>.

    actor-form.component.html (skills)

    <div class="form-group">
            <label for="skill">Skill</label>
            <select class="form-control" id="skill" required>
              @for (skill of skills; track $index) {
                <option [value]="skill">{{ skill }}</option>
              }
            </select>
          </div>

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

Привязка input-контролов к свойствам данных

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

Директива ngModel, объявленная в FormsModule, позволяет привязывать контролы в template-driven форме к свойствам модели данных. Когда директива включается синтаксисом двусторонней привязки [(ngModel)], Angular может отслеживать значение и взаимодействие пользователя с контролом и синхронизировать представление с моделью.

  1. Отредактируйте файл шаблона actor-form.component.html.
  2. Найдите тег <input> рядом с меткой Name.
  3. Добавьте директиву ngModel с синтаксисом двусторонней привязки [(ngModel)]="...".

actor-form.component.html (excerpt)

<input type="text" class="form-control" id="name" required [(ngModel)]="model.name" name="name" />
  TODO: remove this: {{ model.name }}

ПОЛЕЗНО: В этом примере после каждого тега input временно стоит диагностическая интерполяция {{model.name}}, чтобы показать текущее значение соответствующего свойства. Комментарий напоминает удалить диагностические строки, когда вы закончите наблюдать работу двусторонней привязки.

Доступ к общему статусу формы

Когда вы импортировали FormsModule в компонент, Angular автоматически создал и прикрепил директиву NgForm к тегу <form> в шаблоне (потому что у NgForm селектор form, совпадающий с элементами <form>).

Чтобы получить доступ к NgForm и общему статусу формы, объявите template reference variable.

  1. Отредактируйте файл шаблона actor-form.component.html.

  2. Обновите тег <form> template reference variable #actorForm и задайте её значение следующим образом.

    actor-form.component.html (excerpt)

    <form #actorForm="ngForm">

    Template variable actorForm теперь ссылается на экземпляр директивы NgForm, управляющий формой в целом.

  3. Запустите приложение.

  4. Начните вводить текст в поле Name.

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

Диагностическая строка с интерполированными значениями показывает, что значения действительно текут из поля ввода в модель и обратно.

Именование элементов управления

Когда на элементе используется [(ngModel)], для этого элемента нужно определить атрибут name. Angular использует назначенное имя, чтобы зарегистрировать элемент у директивы NgForm, прикреплённой к родительскому элементу <form>.

В примере к элементу <input> добавлен атрибут name со значением "name", что логично для имени актёра. Подойдёт любое уникальное значение, но описательное имя удобнее.

  1. Добавьте аналогичные привязки [(ngModel)] и атрибуты name к Studio и Skill.
  2. Теперь можно удалить диагностические сообщения с интерполированными значениями.
  3. Чтобы подтвердить, что двусторонняя привязка работает для всей модели актёра, добавьте в начало шаблона компонента новую текстовую привязку с pipe json, которая сериализует данные в строку.

После этих правок шаблон формы должен выглядеть так:

actor-form.component.html (excerpt)

{{ model | json }}
      <div class="form-group">
        <label for="name">Name</label>
        <input
          type="text"
          class="form-control"
          id="name"
          required
          [(ngModel)]="model.name"
          name="name"
        />
      </div>

      <div class="form-group">
        <label for="studio">Studio</label>
        <input
          type="text"
          class="form-control"
          id="studio"
          [(ngModel)]="model.studio"
          name="studio"
        />
      </div>

      <div class="form-group">
        <label for="skill">Skill</label>
        <select class="form-control" id="skill" required [(ngModel)]="model.skill" name="skill">
          @for (skill of skills; track $index) {
            <option [value]="skill">{{ skill }}</option>
          }
        </select>
      </div>

Обратите внимание:

  • У каждого элемента <input> есть свойство id. Его использует атрибут for элемента <label>, чтобы связать метку с input-контролом. Это стандартная возможность HTML.

  • У каждого элемента <input> также есть обязательное свойство name, которое Angular использует для регистрации контрола в форме.

Когда вы наблюдали эффекты, текстовую привязку {{ model | json }} можно удалить.

Отслеживание состояний формы

Angular применяет класс ng-submitted к элементам form после отправки формы. Этот класс можно использовать, чтобы изменить стиль формы после отправки.

Отслеживание состояний контрола

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

В следующей таблице описаны имена классов, которые Angular применяет в зависимости от состояния контрола.

Состояния Класс если true Класс если false
Контрол был посещён. ng-touched ng-untouched
Значение контрола изменилось. ng-dirty ng-pristine
Значение контрола валидно. ng-valid ng-invalid

Angular также применяет класс ng-submitted к элементам form при отправке, но не к контролам внутри элемента form.

Эти CSS-классы используют, чтобы задать стили контрола в зависимости от его статуса.

Наблюдение за состояниями контрола

Чтобы увидеть, как фреймворк добавляет и удаляет классы, откройте инструменты разработчика браузера и осмотрите элемент <input>, представляющий имя актёра.

  1. С помощью инструментов разработчика браузера найдите элемент <input>, соответствующий полю Name. Видно, что у элемента несколько CSS-классов в дополнение к "form-control".

  2. При первом открытии классы указывают, что значение валидно, не менялось с инициализации или сброса и контрол не посещался с инициализации или сброса.

    <input class="form-control ng-untouched ng-pristine ng-valid" />
  3. Выполните следующие действия с полем Name <input> и наблюдайте, какие классы появляются.

    • Посмотрите, но не трогайте. Классы указывают, что контрол untouched, pristine и valid.

    • Кликните внутри поля имени, затем кликните снаружи. Контрол теперь посещён, и у элемента класс ng-touched вместо ng-untouched.

    • Добавьте слэши в конец имени. Теперь контрол touched и dirty.

    • Сотрите имя. Значение становится невалидным, поэтому класс ng-invalid заменяет ng-valid.

Визуальная обратная связь для состояний

Пара ng-valid/ng-invalid особенно интересна, потому что при невалидных значениях нужен сильный визуальный сигнал. Также нужно отмечать обязательные поля.

Обязательные поля и невалидные данные можно отметить одновременно цветной полосой слева от поля ввода.

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

  1. Добавьте определения для CSS-классов ng-*.

  2. Добавьте эти определения классов в новый файл forms.css.

  3. Добавьте новый файл в проект рядом с index.html:

    forms.css

    .ng-valid[required], .ng-valid.required  {
      border-left: 5px solid #42A948; /* green */
    }
    
    .ng-invalid:not(form)  {
      border-left: 5px solid #a94442; /* red */
    }
    
  4. В файле index.html обновите тег <head>, чтобы подключить новую таблицу стилей.

    index.html (styles)

    <link rel="stylesheet" href="assets/forms.css" />

Показ и скрытие сообщений об ошибках валидации

Поле Name обязательно, и его очистка делает полосу красной. Это указывает, что что-то не так, но пользователь не знает, что именно и что делать. Можно дать полезное сообщение, проверяя состояние контрола и реагируя на него.

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

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

  1. Add a local reference to the input

    Расширьте тег input template reference variable, чтобы из шаблона обращаться к Angular-контролу поля ввода. В примере переменная — #name="ngModel".

    Template reference variable (#name) задаётся как "ngModel", потому что это значение свойства NgModel.exportAs. Это свойство говорит Angular, как связать reference variable с директивой.

  2. Add the error message

    Добавьте <div> с подходящим сообщением об ошибке.

  3. Make the error message conditional

    Показывайте или скрывайте сообщение об ошибке, привязав свойства контрола name к свойству hidden элемента <div> сообщения.

  4. actor-form.component.html (hidden-error-msg)

    <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
  5. Add a conditional error message to name

    Добавьте условное сообщение об ошибке к полю name, как в следующем примере.

    actor-form.component.html (excerpt)

    <label for="name">Name</label>
            <input
              type="text"
              class="form-control"
              id="name"
              required
              [(ngModel)]="model.name"
              name="name"
              #name="ngModel"
            />
            <div [hidden]="name.valid || name.pristine" class="alert alert-danger">
              Name is required
            </div>

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

Возможно, сообщение нужно показывать только когда пользователь делает невалидное изменение. Скрытие сообщения, пока контрол в состоянии pristine, достигает этой цели. Значимость этого выбора станет ясна, когда в следующем шаге вы добавите нового актёра в форму.

Добавление нового актёра

Это упражнение показывает, как реагировать на нативное событие клика HTML-кнопки, добавляя данные в модель. Чтобы пользователи формы могли добавить нового актёра, добавьте кнопку New Actor, реагирующую на событие click.

  1. В шаблоне разместите элемент <button> "New Actor" внизу формы.

  2. В файле компонента добавьте метод создания актёра в модель данных актёра.

    actor-form.component.ts (New Actor method)

    newActor() {
        this.model = new Actor(42, '', '');
      }
  3. Привяжите событие click кнопки к методу создания актёра newActor().

    actor-form.component.html (New Actor button)

    <button type="button" class="btn btn-default" (click)="newActor()">New Actor</button>
  4. Снова запустите приложение и нажмите кнопку New Actor.

    Форма очищается, и обязательные полосы слева от полей ввода красные, указывая на невалидные свойства name и skill. Обратите внимание, что сообщения об ошибках скрыты. Это потому, что форма pristine: вы ещё ничего не меняли.

  5. Введите имя и снова нажмите New Actor.

    Теперь приложение показывает сообщение об ошибке Name is required, потому что поле ввода больше не pristine. Форма помнит, что вы вводили имя до нажатия New Actor.

  6. Чтобы восстановить pristine-состояние контролов формы, сбросьте все флаги императивно, вызвав метод формы reset() после вызова newActor().

    actor-form.component.html (Reset the form)

    <button type="button" class="btn btn-default" (click)="newActor(); actorForm.reset()">
            New Actor
          </button>

    Теперь нажатие New Actor сбрасывает и форму, и флаги её контролов.

Отправка формы с ngSubmit

Пользователь должен иметь возможность отправить эту форму после заполнения. Кнопка Submit внизу формы сама по себе ничего не делает, но запускает событие отправки формы из-за своего типа (type="submit").

Чтобы отреагировать на это событие, выполните следующие шаги.

  1. Listen to ngOnSubmit

    Привяжите событие ngSubmit формы к методу onSubmit() компонента actor-form.

    actor-form.component.html (ngSubmit)

    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
  2. Bind the disabled property

    Используйте template reference variable #actorForm, чтобы получить доступ к форме, содержащей кнопку Submit, и создайте привязку события.

    Привяжите свойство формы, указывающее на её общую валидность, к свойству disabled кнопки Submit.

    actor-form.component.html (submit-button)

    <button type="submit" class="btn btn-success" [disabled]="!actorForm.form.valid">
            Submit
          </button>
  3. Run the application

    Обратите внимание, что кнопка включена — хотя пока ничего полезного не делает.

  4. Delete the Name value

    Это нарушает правило "required", поэтому показывается сообщение об ошибке — и обратите внимание, что кнопка Submit также отключается.

    Не пришлось явно связывать состояние включения кнопки с валидностью формы. FormsModule сделал это автоматически, когда вы определили template reference variable на расширенном элементе формы, а затем сослались на эту переменную в контроле кнопки.

Реакция на отправку формы

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

  1. Wrap the form

    Оберните всю форму в <div> и привяжите его свойство hidden к свойству ActorFormComponent.submitted.

    actor-form.component.html (excerpt)

    <div [hidden]="submitted">
        <h1>Actor Form</h1>
        <form (ngSubmit)="onSubmit()" #actorForm="ngForm">
    
        <!-- ... all of the form ... -->
      </form>
    </div>

    Основная форма видна с самого начала, потому что свойство submitted равно false, пока вы не отправите форму, как показывает этот фрагмент из ActorFormComponent:

    actor-form.component.ts (submitted)

    submitted = false;
    
      onSubmit() {
        this.submitted = true;
      }

    Когда вы нажимаете кнопку Submit, флаг submitted становится true, и форма исчезает.

  2. Add the submitted state

    Чтобы показать что-то другое, пока форма в состоянии submitted, добавьте следующий HTML ниже новой обёртки <div>.

    actor-form.component.html (excerpt)

    <div [hidden]="!submitted">
        <h2>You submitted the following:</h2>
        <div class="row">
          <div class="col-xs-3">Name</div>
          <div class="col-xs-9">{{ model.name }}</div>
        </div>
        <div class="row">
          <div class="col-xs-3">Studio</div>
          <div class="col-xs-9">{{ model.studio }}</div>
        </div>
        <div class="row">
          <div class="col-xs-3">Skill</div>
          <div class="col-xs-9">{{ model.skill }}</div>
        </div>
        <br />
        <button type="button" class="btn btn-primary" (click)="submitted = false">Edit</button>
      </div>

    Этот <div>, показывающий актёра только для чтения с интерполяционными привязками, появляется только пока компонент в состоянии submitted.

    Альтернативное отображение включает кнопку Edit, событие click которой привязано к выражению, сбрасывающему флаг submitted.

  3. Test the Edit button

    Нажмите кнопку Edit, чтобы вернуть отображение к редактируемой форме.

Итог

Форма Angular, рассмотренная на этой странице, использует следующие возможности фреймворка для поддержки изменения данных, валидации и многого другого.

  • HTML-шаблон формы Angular
  • Класс компонента формы с декоратором @Component
  • Обработка отправки формы привязкой к событию NgForm.ngSubmit
  • Template-reference variables, такие как #actorForm и #name
  • Синтаксис [(ngModel)] для двусторонней привязки данных
  • Использование атрибутов name для валидации и отслеживания изменений элементов формы
  • Свойство valid reference variable на input-контролах указывает, валиден ли контрол или нужно показать сообщения об ошибках
  • Управление состоянием включения кнопки Submit привязкой к валидности NgForm
  • Пользовательские CSS-классы, дающие пользователям визуальную обратную связь о невалидных контролах

Вот код финальной версии приложения: