Статьи

Введение в формы в Angular 4: Шаблонно-управляемые формы

Конечный продукт
Что вы будете создавать

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

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

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

Angular, будучи полноценной интерфейсной средой, имеет свой собственный набор библиотек для построения сложных форм. В последней версии Angular есть две мощные стратегии построения форм. Они есть:

  • управляемые шаблоном формы
  • модельные или реактивные формы

Обе технологии принадлежат библиотеке @angular/forms и основаны на одних и тех же классах управления формой. Тем не менее, они заметно отличаются по своей философии, стилю программирования и технике. Выбор одного зависит от вашего личного вкуса, а также от сложности формы, которую вы пытаетесь создать. На мой взгляд, вы должны сначала попробовать оба подхода, а затем выбрать тот, который соответствует вашему стилю и проекту под рукой.

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

Подход на основе шаблонов — это стратегия, заимствованная из эпохи AngularJS. На мой взгляд, это самый простой метод построения форм. Как это работает? Мы будем использовать некоторые угловые директивы.

Директивы позволяют вам привязывать поведение к элементам в DOM.
угловая документация

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

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

  • Добавьте FormsModule в app.module.ts .
  • Создайте класс для модели User.
  • Создайте исходные компоненты и макет для формы регистрации.
  • Используйте директивы угловой формы, такие как ngModel , ngModelGroup и ngForm .
  • Добавьте проверку с использованием встроенных валидаторов.
  • Отображать ошибки проверки значимым.
  • Обработайте ngSubmit формы, используя ngSubmit .

Давайте начнем.

Код для этого проекта доступен на моем репозитории GitHub . Загрузите почтовый индекс или клонируйте репо, чтобы увидеть его в действии. Если вы предпочитаете начинать с нуля, убедитесь, что у вас установлен Angular CLI. Используйте команду ng для создания нового проекта.

1
$ ng new SignupFormProject

Затем создайте новый компонент для RegistrationForm.

1
ng generate component SignupForm

Замените содержимое app.component.html следующим:

1
<app-signup-form> </app-signup-form>

Вот структура каталогов для каталога src / . Я удалил некоторые ненужные файлы, чтобы все было просто.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
.
├── app
│  ├── app.component.css
│  ├── app.component.html
│  ├── app.component.ts
│  ├── app.module.ts
│  ├── signup-form
│  │  ├── signup-form.component.css
│  │  ├── signup-form.component.html
│  │  └── signup-form.component.ts
│  └── User.ts
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── tsconfig.app.json
└── typings.d.ts

Как видите, каталог для компонента SignupForm был создан автоматически. Вот куда пойдет большая часть нашего кода. Я также создал новый User.ts для хранения нашей модели User.

Прежде чем мы углубимся в фактический шаблон компонента, нам нужно иметь абстрактное представление о том, что мы создаем. Итак, вот структура формы, которую я имею в виду. Форма регистрации будет иметь несколько полей ввода, элемент select и элемент checkbox.

Шаблон HTML

Вот HTML-шаблон, который мы будем использовать для нашей страницы регистрации.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<div class=»row custom-row»>
  <div class= «col-sm-5 custom-container jumbotron»>
       
    <form class=»form-horizontal»>
        <fieldset>
          <legend>SignUp</legend>
         
            <!— Email Block —>
            <div class=»form-group»>
              <label for=»inputEmail»>Email</label>
              <input type=»text»
                id=»inputEmail»
                placeholder=»Email»>
            </div>
            <!— Password Block —>
            <div class=»form-group»>
              <label for=»inputPassword»>Password</label>
              <input type=»password»
                id=»inputPassword»
                placeholder=»Password»>
            </div>
     
            <div class=»form-group»>
              <label for=»confirmPassword» >Confirm Password</label>
              <input type=»password»
                id=»confirmPassword»
                placeholder=»Password»>
            </div>
             
            <!— Select gender Block —>
            <div class=»form-group»>
              <label for=»select»>Gender</label>
                <select id=»select»>
                  <option>Male</option>
                  <option>Female</option>
                  <option>Other</option>
                </select>
            </div>
             
            <!— Terms and conditions Block —>
             <div class=»form-group checkbox»>
              <label>
                <input type=»checkbox»> Confirm that you’ve read the Terms and
                Conditions
              </label>
            </div>
            
           <!— Buttons Block —>
            <div class=»form-group»>
                <button type=»reset» class=»btn btn-default»>Cancel</button>
                <button type=»submit» class=»btn btn-primary»>Submit</button>
            </div>
        </fieldset>
    </form>
  </div>
</div>

Классы CSS, используемые в шаблоне HTML, являются частью библиотеки Bootstrap, используемой для создания красивых вещей. Поскольку это не учебник по дизайну, я не буду много говорить об аспектах CSS формы, если в этом нет необходимости.

Чтобы использовать управляемые шаблоном директивы форм, нам нужно импортировать FormsModule из @angular/forms и добавить его в массив app.module.ts в app.module.ts .

01
02
03
04
05
06
07
08
09
10
11
12
13
import { FormsModule } from ‘@angular/forms’;
 
@NgModule({
 .
 .
 imports: [
    BrowserModule,
    FormsModule
  ],
  .
  .
})
export class AppModule { }

Затем создайте класс, который будет содержать все свойства объекта User. Мы можем использовать интерфейс и реализовать его в компоненте или использовать класс TypeScript для модели.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
export class User {
 
    id: number;
    email: string;
    //Both the passwords are in a single object
    password: {
      pwd: string;
      confirmPwd: string;
    };
    gender: string;
    terms: boolean;
 
    constructor(values: Object = {}) {
      //Constructor initialization
      Object.assign(this, values);
  }
 
}

Теперь создайте экземпляр класса в компоненте RegistrationForm. Я также объявил дополнительную собственность для пола.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Component, OnInit } from ‘@angular/core’;
// Import the User model
import { User } from ‘./../User’;
 
@Component({
  selector: ‘app-signup-form’,
  templateUrl: ‘./signup-form.component.html’,
  styleUrls: [‘./signup-form.component.css’]
})
export class SignupFormComponent implements OnInit {
 
  //Property for the gender
  private gender: string[];
  //Property for the user
  private user:User;
 
  ngOnInit() {
 
    this.gender = [‘Male’, ‘Female’, ‘Others’];
    //Create a new user object
    this.user = new User({
        email:»», password: { pwd: «» , confirm_pwd: «»},
        gender: this.gender[0], terms: false});
    }
 
}

Для файла signup-form.component.html я собираюсь использовать тот же шаблон HTML, который обсуждался выше, но с небольшими изменениями. Форма регистрации имеет поле выбора со списком параметров. Хотя это работает, мы сделаем это угловым путем, просматривая список с ngFor директивы ngFor .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div class=»row custom-row»>
  <div class= «col-sm-5 custom-container jumbotron»>
       
    <form class=»form-horizontal»>
        <fieldset>
          <legend>SignUp</legend>
.
.
            <!— Gender Block —>
            <div class=»form-group»>
              <label for=»select»>Gender</label>
                   <select id=»select»>
                      
                     <option *ngFor = «let g of gender»
                       [value] = «g»> {{g}}
                     </option>
                   </select>
               </div>
.
.
    </fieldset>
    </form>
  </div>
</div>

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

Есть несколько способов сделать это. Позвольте мне сначала познакомить вас с ngModel и ngForm .

ngForm и ngModel — это угловые директивы, которые необходимы для создания форм на основе шаблонов. Давайте начнем с ngForm первую очередь. Вот выдержка о ngForm из Angular Docs.

Директива NgForm дополняет элемент form дополнительными функциями. Он содержит элементы управления, созданные вами для элементов с директивой ngModel и атрибутом name , и отслеживает их свойства, включая их достоверность. Он также имеет собственное valid свойство, которое имеет значение true, только если каждый содержащийся элемент управления является действительным

Сначала обновите форму с ngForm директивы ngForm :

1
2
3
4
5
6
<form
  class=»form-horizontal»
  #signupForm = «ngForm»>
.
.
</form>

#signupForm является ссылочной переменной шаблона, которая ссылается на директиву ngForm которая управляет всей формой. В приведенном ниже примере демонстрируется использование ссылочного объекта ngForm для проверки.

1
2
3
4
5
6
<button
   type=»submit»
   class=»btn btn-success»
   [disabled]=»!signupForm.form.valid»>
     Submit
</button>

Здесь signupForm.form.valid вернет false, если все элементы формы не пройдут соответствующие проверки. Кнопка отправки будет отключена, пока форма не будет действительной.

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

  1. [(NgModel)]
  2. [NgModel]
  3. ngModel

Давайте начнем с первого.

[(ngModel)] выполняет двустороннюю привязку для чтения и записи значений управления вводом. Если используется директива [(ngModel)] , поле ввода получает начальное значение из класса связанных компонентов и обновляет его обратно всякий раз, когда обнаруживается любое изменение значения элемента управления вводом (при нажатии клавиши и нажатии кнопки). Изображение ниже лучше описывает процесс двустороннего связывания.

Двустороннее связывание с ngModel

Вот код для поля ввода электронной почты:

1
2
3
4
5
6
7
8
<div class=»form-group»>
     <label for=»inputEmail»>Email</label>
     <input type=»text»
       [(ngModel)] = «user.email»
       id=»inputEmail»
       name=»email»
       placeholder=»Email»>
   </div>

[(ngModel)] = "user.email" привязывает свойство электронной почты пользователя к входному значению. Я также добавил атрибут name и установил name="email" . Это важно, и вы получите ошибку, если вы не объявили атрибут name при использовании ngModel.

Аналогичным образом добавьте [(ngModel)] и атрибут уникального имени для каждого элемента формы. Ваша форма должна выглядеть примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
.
.
.
      <div ngModelGroup=»password»>
        <div class=»form-group» >
          <label for=»inputPassword»>Password</label>
          <input type=»password»
           [(ngModel)] = «user.password.pwd» name=»pwd»
           placeholder=»Password»>
        </div>
 
        <div class=»form-group»>
          <label for=»confirmPassword» >Confirm Password</label>
          <input type=»password»
            [(ngModel)] = «user.password.confirmPwd» name=»confirmPwd»
            placeholder=»Confirm Password»>
        </div>
        </div>
        <div class=»form-group»>
          <label for=»select»>Gender</label>
            <select id=»select»
              [(ngModel)] = «user.gender» name = «gender»>
               
              <option *ngFor = «let g of gender»
                [value] = «g»> {{g}}
              </option>
            </select>
        </div>
         
     .
     .
     .

ngModelGroup используется для группировки похожих полей формы, чтобы мы могли выполнять проверки только для этих полей формы. Поскольку оба поля пароля связаны, мы поместим их в одну группу ngModelGroup. Если все работает должным образом, user свойство, связанное с компонентом, должно хранить все значения элемента управления формы. Чтобы увидеть это в действии, добавьте следующее после тега формы:

1
{{user |

JsonPipe пользовательское свойство через JsonPipe чтобы отобразить модель как JSON в браузере. Это полезно для отладки и регистрации. Вы должны увидеть вывод в формате JSON следующим образом.

Пример вывода в формате JSON

Значения поступают из вида в модель. А как же наоборот? Попробуйте инициализировать пользовательский объект с некоторыми значениями.

1
2
3
4
5
6
7
this.user = new User({
   //initialized with some data
   email:»thisisfromthemodel@example.com»,
   password: { pwd: «» , confirm_pwd: «»},
   gender: this.gender[0]
    
   });

И они автоматически появляются в виде:

1
2
3
4
{ «email»: «thisisfromthemodel@example.com»,
«password»: { «pwd»: «», «confirm_pwd»: «» },
«gender»: «Male»
}

Синтаксис двустороннего связывания [(ngModel)] помогает вам создавать формы без особых усилий. Тем не менее, у него есть определенные недостатки; следовательно, есть альтернативный подход, который использует ngModel или [ngModel] .

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

Поэтому замените все экземпляры [(ngModel)] = " " на ngModel . Мы сохраним атрибут name потому что все три версии ngModel нуждаются в атрибуте name для работы.

1
2
3
4
5
6
7
8
<div class=»form-group»>
         <label for=»inputEmail»>Email</label>
         <input type=»text»
           ngModel
           id=»inputEmail»
           name=»email»
           placeholder=»Email»>
       </div>

С помощью ngModel значение атрибута name станет ключом ссылочного объекта signupForm который мы создали ранее. Так, например, signupForm.value.email будет хранить контрольное значение для идентификатора электронной почты.

Заменить {{user | json}} {{user | json}} с {{signupForm.value | json }} {{signupForm.value | json }} потому что здесь хранится все состояние.

Что если вам нужно установить начальное состояние из компонента связанного класса? Вот что [ngModel] делает для вас.

Одностороннее связывание с ngModel

Здесь данные передаются из модели в представление. Внесите следующие изменения в синтаксис для использования односторонней привязки:

1
2
3
4
5
6
7
8
<div class=»form-group»>
      <label for=»inputEmail»>Email</label>
      <input type=»text»
        [ngModel] = «user.email»
        id=»inputEmail»
        name=»email»
        placeholder=»Email»>
</div>

Так какой подход выбрать? Если вы используете [(ngModel)] и ngForm вместе, вы в конечном итоге будете иметь два состояния для поддержки — user и signupForm.value — и это может привести к путанице.

1
2
3
4
5
6
7
8
9
{ «email»: «thisisfromthemodel@example.com»,
«password»: { «pwd»: «thisispassword», «confirm_pwd»: «thisispassword» },
«gender»: «Male»
} //user.value
 
{ «email»: «thisisfromthemodel@example.com»,
«password»: { «pwd»: «thisispassword», «confirm_pwd»: «thisispassword» },
«gender»: «Male»
} //signupForm.value

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

Вот наши требования к валидации.

  • Все формы управления обязательны.
  • Отключите кнопку отправки, пока все поля ввода не будут заполнены.
  • Поле электронной почты должно содержать идентификатор электронной почты.
  • Поле пароля должно иметь минимальную длину 8.
  • И пароль, и подтверждение должны совпадать.
Форма с проверкой с использованием шаблонно-управляемых форм
Наша форма с подтверждением на месте

Первое легко. Вы должны добавить required атрибут проверки для каждого элемента формы, например так:

1
2
3
4
5
<input type=»text»
   [ngModel] = «user.email» name=»email»
   #email = «ngModel»
   placeholder=»Email»
   required>

Помимо required атрибута я также экспортировал новую справочную переменную шаблона #email . Это сделано для того, чтобы вы могли получить доступ к элементу управления Angular поля ввода из самого шаблона. Мы будем использовать его для отображения ошибок и предупреждений. Теперь используйте свойство disabled кнопки, чтобы отключить кнопку:

1
2
3
4
5
6
<button
   type=»submit»
   class=»btn btn-success»
   [disabled]=»!signupForm.form.valid»>
     Submit
</button>

Чтобы добавить ограничение на электронную почту, используйте атрибут pattern, который работает с полями ввода. Шаблоны используются для указания регулярных выражений, подобных приведенному ниже:

1
pattern=»[a-z0-9._%+-]+@[a-z0-9.-]+\.[az]{2,3}$»

Для поля пароля все, что вам нужно сделать, это добавить minlength=" " :

1
2
3
4
5
6
7
8
<input type=»password»
           ngModel
           id=»inputPassword»
           name=»pwd»
           #pwd = «ngModel»
           placeholder=»Password»
           minlength=»8″
           required>

Чтобы отобразить ошибки, я собираюсь использовать условную директиву ngIf для элемента div. Давайте начнем с поля управления вводом для электронной почты:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
<div class=»form-group»>
              <label for=»inputEmail»>Email</label>
              <input type=»text»
                [ngModel] = «user.email» name=»email»
                #email = «ngModel» id=»inputEmail»
                placeholder=»Email»
                pattern=»[a-z0-9._%+-]+@[a-z0-9.-]+\.[az]{2,3}$»
                required>
            </div>
 
<!— This is the error section —>
 
<div *ngIf=»email.invalid && (email.dirty || email.touched)»
    class=»alert alert-danger»>
    <div *ngIf = «email.errors?.required»>
        Email field can’t be blank
    </div>
    <div *ngIf = «email.errors?.pattern && email.touched»>
        The email id doesn’t seem right
    </div>
 </div>

Здесь много чего происходит. Начнем с первой строки раздела об ошибках.

1
2
<div *ngIf=»email.invalid && (email.dirty || email.touched)»
    class=»alert alert-danger»>

Помните переменную #email которую мы экспортировали ранее? Он содержит некоторое количество информации о состоянии управления вводом в поле электронной почты. Это включает в себя: email.valid , email.invalid , email.dirty , email.pristine , email.touched , email.untouched и email.errors . Изображение ниже подробно описывает каждое из этих свойств.

Различные свойства класса для отображения ошибок

Таким образом, элемент div с *ngIf будет отображаться только в том случае, если электронная почта недействительна. Тем не менее, пользователь получит сообщение об ошибке, когда поля ввода не заполнены, даже до того, как они получат возможность редактировать форму.

Чтобы избежать этого сценария, мы добавили второе условие. Ошибка будет отображаться только после посещения элемента управления или изменения значения элемента управления .

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div ngModelGroup=»password» #userPassword=»ngModelGroup» required >
       <div class=»form-group»>
         <label for=»inputPassword»>Password</label>
         <input type=»password»
           ngModel name=»pwd»
           id=»inputPassword» placeholder=»Password»
           minlength =»8″ required>
       </div>
 
       <div class=»form-group»>
         <label for=»confirmPassword» >Confirm Password</label>
         <input type=»password»
           ngModel name=»confirmPwd»
           id=»confirmPassword» placeholder=»Confirm Password»>
       </div>
        
        
       <div *ngIf=»(userPassword.invalid|| userPassword.value?.pwd != userPassword.value?.confirmPwd) && (userPassword.touched)»
       class=»alert alert-danger»>
        
       <div *ngIf = «userPassword.invalid; else nomatch»>
           Password needs to be more than 8 characters
       </div>
           <ng-template #nomatch >
               Passwords don’t match
           </ng-template>
       </div>
   </div>

Это начинает выглядеть немного грязно. Angular имеет ограниченный набор атрибутов валидатора: required , minlength , maxlength и pattern . Чтобы охватить любой другой сценарий, такой как сравнение паролей, вам придется полагаться на вложенные условия ngIf как я делал выше. Или, в идеале, создайте пользовательскую функцию валидатора, о которой я расскажу в третьей части этой серии.

В приведенном выше коде я использовал синтаксис ngIf else который был представлен в последней версии Angular. Вот как это работает:

1
2
3
4
5
<div *ngIf=»isValid;else notvalid»>
    Valid content…
</div>
 
<ng-template #notValid>Not valid content…</ng-template>

Мы почти закончили форму. Теперь нам нужно иметь возможность отправить форму, а управление данными формы следует передать методу компонента, скажем, onFormSubmit() .

1
2
3
4
<form novalidate
(ngSubmit)=»onFormSubmit(signupForm)»
#signupForm=»ngForm»>

Теперь для компонента:

1
2
3
4
5
6
7
  public onFormSubmit({ value, valid}: { value: User, valid: boolean }) {
        this.user = value;
        console.log( this.user);
        console.log(«valid: » + valid);
    }

Я поместил окончательную версию приложения в репозиторий GitHub . Вы можете скачать или клонировать его, чтобы попробовать сами. Я добавил несколько классов начальной загрузки, чтобы сделать форму красивой.

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

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

Поделитесь своими мыслями в комментариях ниже.

Мы создали полное руководство, которое поможет вам изучить JavaScript , независимо от того, начинаете ли вы как веб-разработчик или хотите изучать более сложные темы.