Angular 13: Формы и все что вы о них хотите знать

формы Angular Angular

Приветствую Вас, любители Angular! Сегодня мы поговорим про формы в Angular и все что с ними связано. Пойдем от самого простого к сложному. Разберем двустороннюю привязку, привязку в одностороннем порядке, реактивные формы. Разберем формы FormsModule и ReactiveFormsModule, в чем же их отличие.

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

Вышло видео про Angular

Всем привет, друзья! У меня вышло видео на моем YouTube канале, касательно этой темы. Встраиваю видео сюда, там все то, о чем написано в этой статье. Если вам лень читать или вы просто предпочитаете видео формат — добро пожаловать.

Настройка окружения

Первым делом, нам необходимо развернуть наш проект, для этого в терминале введем команду

ng new angular-forms

Прошу заметить, что если команда не отработала, возможно, что у вас Angular не установлен глобально. Для этого выполните команду:

npm install -g @angular/cli

Если у вас MacOS или Linux, добавьте sudo:

sudo npm install -g @angular/cli

Введите пароль, ангуляр установится и попробуйте снова. Успешное развертывание проекта выглядит так (вас спросят про роутинг и какие стили вы хотите использовать, это уже по желанию. Здесь мы не собираемся использоваться роутинг, а стилей будет минимум, но я выбираю SCSS всегда):

Установка проекта Angular / ng new angular-forms
Установка проекта Angular / ng new angular-forms

Можем переходить в папку нашего проекта и открывать редактор кода. Готовая, чистая структура нашего проекта выглядит так:

Чистая структура проекта в Angular
Чистая структура проекта в Angular

Я не буду вдаваться в подробности о том, какой файл за что отвечает (об этом есть куча видео, может даже когда-то и я запишу). Возможно, что я буду делать это по ходу написания кода. Сейчас нам нужен файл app.component.html, который находится в папке src/app, мы можем полностью очистить его.

Запустим наш проект командой в терминале:

ng serve

Если вы увидели следующий лог:

Лог запуска ng serve в Angular
Лог запуска ng serve в Angular

Тогда все супер! В целом все готово и можно начинать! Ваш сервер работает и доступен в браузере по адресу: http://localhost:4200/

Формы в Angular, начало начал

Алерт! Мальчики и девочки, по ходу статьи я буду оставлять ссылки на официальные документации. Пожалуйста, в первую очередь обращайтесь к ним. Мое повествование — это лишь интерпретация моего видения. Я человек, а следовательно, я могу ошибаться.

Официальная документация Angular: Введение в формы. https://angular.io/guide/forms-overview

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

Формы в Angular — бывают двух типов. Реактивные формы и формы на основе шаблонов. Шаблонные формы используют модуль — FormsModule, а реактивныеReactiveFormsModule.

В чем же их отличие? Ну, во-первых, шаблонные формы — асинхронные, в то время как реактивные — синхронные. Во-вторых, большая часть логики шаблонных модулей написала в самом HTML (app.component.html), в то время, как логика реактивных форм прописана в компоненте (app.component.ts).

Как шаблонные, так и реактивные формы, наследуются от следующих базовых классов (они у них общие).

FormControlFormControl отслеживает значение каждого элемента формы отдельно.
FormGroupFormGroup отслеживает целиком группу состоящую из контроллеров (FormControl).
FormArrayFormArray отслеживает массив состоящий из групп контроллеров (FormGroup, FormControl)
ControlValueAccessor — создает «мост» между FormControl и элементами DOM.

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

Шаблонные формы в Angular

Шаблонные формы используют неявную модель присваивания, в отличии от реактивных форм. Сейчас дальше поймете. Для начала импортируем модуль форм. Для этого нам необходимо в файле app.module.ts в разделе imports добавить FormsModule и он автоматически импортируется. Если этого не произошло, добавьте импорт вручную.

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

Ваш файл app.module.ts должен выглядеть так:

import { FormsModule } from '@angular/forms';
import { FormsModule } from ‘@angular/forms’;

Теперь перейдем в файл app.component.html и создадим такую верстку:

<div>
  <input type="text" [(ngModel)]="name">
</div>

Здесь ключевое [(ngModel)]="name", ngModel — отвечает за привязку, а name — это обычная переменная, которую мы сейчас создадим в файле app.component.ts:

  name = 'Denis';
Angular forms
Angular forms

Откроем браузер по адресу localhost:4200 и проверим, что все работает:

AngularForms

Супер, привязка сработала! Теперь вводимые данные хранятся в переменной name. Я это показал, чтоб вы просто понимали как происходит привязка данных вне формы (причем, привязка двухсторонняя).

Чтобы это проверить, в app.component.html в любое место добавьте вывод нашей переменной {{ name }}, начните изменять данные и вы увидите результат.

Код должен выглядеть примерно так:

<div><input [(ngModel)]="name" />{{ name }}</div>

А отображается так:

Angular 13: Формы и все что вы о них хотите знать

Давайте теперь создадим полноценную форму, с именем, профессией и возрастом. Для этого перейдем в файл app.component.html и создадим такую верстку:

<div>
  <form>
    <div>Имя: <input name="name" id="name" type="text" /></div>
    <div>Возраст: <input name="age" id="age" type="text" /></div>
    <div>Профессия: <input name="profession" id="profession" type="text" /></div>
  </form>
</div>

Казалось бы, ничего необычного. Этот код вам знаком уже из курсов по HTML. Самая обычная верстка. Но как нам привязать к ней данные и получать эти данные? Как нам узнать, что ввел пользователь?

Во-первых, давайте дадим уникальное имя для нашей формы.

<form #myForm="ngForm" (ngSubmit)="submitForm(myForm)">

Объясняю: #myForm — это имя нашей формы, "ngForm" — это привязка к шаблонным формам Angular. Так мы говорим ангуляру, что теперь ему нужно следить за этой формой. (ngSubmit) — отправка нашей формы, submitForm(myForm)обработчик события, обычная функция в файле app.component.ts, которая будет обрабатывать нашу отправку, туда передается наша форма myForm.

В самый конец можно добавить кнопку отправки:

<button type="submit">Отправить</button>

Так должен выглядеть наш итоговый код app.component.html:

<div>
  <form #myForm="ngForm" (ngSubmit)="submitForm(myForm)">
    <div>Имя: <input name="name" id="name" type="text" /></div>
    <div>Возраст: <input name="age" id="age" type="text" /></div>
    <div>Профессия: <input name="profession" id="profession" type="text" /></div>
    <button type="submit">Отправить</button>
  </form>
</div>

А теперь перейдем в файл app.component.ts и создадим обработчик события внутри нашего класса.

submitForm(form: NgForm) {
    console.log(form);
}

form: NgForm — здесь мы говорим, что ожидаем нашу форму myForm, тип которой принадлежит классу ngForm. Она должна автоматически импортироваться, если этого не произошло, добавьте в самый вверх вашего файла app.component.ts:

import { NgForm } from '@angular/forms';

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

Angular 13: Формы и все что вы о них хотите знать

Форма отлично отправляется, все супер. Но! Мы не получаем значения наших полей, на скриншоте это объект value: {} и он пустой. В чем же дело? Дело в том, что нам надо сказать ангуляру, чтобы он следил за нужными нам полями. Как это сделать?

Для этого в каждый наш импут необходимо добавить ngModel, выглядеть должно так:

<div>
  <form #myForm="ngForm" (ngSubmit)="submitForm(myForm)">
    <div>Имя: <input name="name" id="name" type="text" ngModel /></div>
    <div>Возраст: <input name="age" id="age" type="text" ngModel /></div>
    <div>Профессия: <input name="profession" id="profession" type="text" ngModel /></div>
    <button type="submit">Отправить</button>
  </form>
</div>

Попробуем еще раз? Снова перейдем в браузер, заполним форму, откроем консоль и нажмем отправить.

Формы в Angular
Формы в Angular

Ну вот, уже другое дело. Мы получаем данные о том, что ввел пользователь. Неявно, «под капотом», каждому из полей присваивается FormControl, если это вам ни о чем не говорит, подождите, скоро дойдем до реактивных форм, там мы их будем прописывать сами!

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

submitForm(form: NgForm) {
    console.log(form.value.name);
    console.log(form.value.age);
    console.log(form.value.profession);
}

Откроем браузер и проделаем все то, что делали выше:

Angular Forms
Формы Ангуляра

Если вы хотите «на месте» отображать эти же данные прямо в шаблоне, вы можете вставить в ваш код (app.component.html):

<div>
  {{myForm.value.name}}
  {{myForm.value.age}}
  {{myForm.value.profession}}
</div>

Начните менять значения в инпутах и вы увидите, как моментально меняются и отображаемые данные. Для этого вам даже не надо нажимать кнопку отправить, данные меняются «на лету».

Все о формах в Angular
Все о формах в Angular

Что если мы хотим очистить нашу форму после отправки? Для этого в нашу функцию submitForm() необходимо добавить:

form.reset();

Но, это не единственный способ очистки. Что если мы хотим очищать форму отдельной клавишей? Добавим кнопку «Очистить», рядом с кнопкой отправить.

<button type="submit">Отправить</button>
<button (click)="myForm.reset()">Очистить</button>

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

<form #myForm="ngForm" (ngSubmit)="submitForm(myForm)">

В функцию submitForm(myForm) мы передаем нашу форму при отправке, однако, можно этого не делать. Можно наблюдать за формой из самого компонента app.component.ts. Давайте исправим эту строку, перестанем передавать нашу форму.

<form #myForm="ngForm" (ngSubmit)="submitForm()">

А в самом компоненте перестанем ее принимать, иначе TypeScript будет ругаться на нас.

submitForm() {
    console.log(form.value.name);
    console.log(form.value.age);
    console.log(form.value.profession);
}

Вы могли заметить, что строки с консоль логом начали подсвечиваться, оно и понятно. Ведь откуда теперь нам брать данные о состоянии формы? Вот мы и плавно подошли к декоратору @ViewChild.

Декоратор @ViewChild может отслеживать дочерние элементы нашего DOM, которые вы ему скажите отслеживать. Декораторов может быть много. Ладно, давайте я лучше покажу вам. В нашем компоненте, необходимо написать следующий код:

@ViewChild('myForm') form!: NgForm;

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

import { Component, ViewChild } from '@angular/core';

Окей, давайте же разберем, что мы такого написали. @ViewChild('myForm')— просим наш декоратор найти в нашей верстке элемент с именем #myForm и следить за ним. Далее — form!: NgFormform — это мы даем новое название нашей форме в компоненте, то, как мы к ней будем обращаться (название может быть любое). Ну и NgForm — определяем тип нашей формы. (Это уже относится к TypeScript, можете написать any пока что (что не рекомендуется конечно, но если вы не знакомы с typescript и только изучаете, то ок)).

Ну вот, теперь мы следим за нашей формой и получаем все данные о ней в переменную form. Давайте очистим нашу функцию submitForm() и добавим следующий код.

submitForm() {
    console.log(this.form.value);
}

Да, теперь мы можем просто обращаться к нашей форме через this.form. Результат будет такой же. Глянем в наш браузер:

Angular 13: Формы и все что вы о них хотите знать

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

Реактивные формы в Angular

Во-первых, давайте очистим все то, что мы писали до этого. Сделаем наши компоненты пустыми и начнем с нуля. Очищаем компонент app.component.ts и оставляем только это:

Реактивные формы в Angular
Реактивные формы в Angular

А в файле app.component.html вообще удалим все. Чтобы все было наглядно и с нуля. Теперь у нас первоначальный вид нашего приложения. Первым делом, нам необходимо импортировать ReactiveFormsModule. Переходим в файл app.module.ts и импортируем его. Должно выглядеть так:

Импортируем ReactiveFormsModule
Импортируем ReactiveFormsModule

Переходим к нашей верстке, открываем наш файл app.component.html и пишем самую обычную HTML форму.

<div>
  <form>
    <div>Имя: <input /></div>
    <div>Возраст: <input /></div>
    <div>Профессия: <input /></div>
    <button>Отправить</button>
  </form>
</div>

Отлично! А теперь переходим к более интересным вещам. Как я уже говорил, реактивные формы, в отличии от шаблонных, пишутся полностью в app.component.ts, а в верстку уже подставляются лишь названия переменных.

Открываем файл компонента app.component.ts и начинаем создавать форму в нашем классе.

myForm: FormGroup = new FormGroup({
  name: new FormControl(''),
  age: new FormControl(''),
  profession: new FormControl(''),
});

Уже видите разницу с шаблонными формами? Окей, что же мы тут понаписали, давайте разбираться. myForm: FormGroup = new FormGroup : myForm — название нашей формы, которая принадлежит классу FormGroup, new FormGroup — сразу же «на месте» создаем новую форму.

Далее у нас идет перечисление полей. У нас есть 3 поля, name, age и profession, в которые будут записываться наши данные из верстки. new FormControl('') — помните я говорил, что мы будем создавать их вручную?

В шаблонных формах они создаются неявно, здесь это происходит явно. Мы создаем поле, которое имеет пустое значение. Мы могли бы написать так: 'name': new FormControl('Denis') и тогда бы в поле инпута по умолчанию отображалось бы Denis. (Задаем значение по умолчанию).

Окей, как же нам это все связать с версткой? Открываем файл app.component.html.

<div>
  <form [formGroup]="myForm">
    <div>Имя: <input formControlName="name" /></div>
    <div>Возраст: <input formControlName="age" /></div>
    <div>Профессия: <input formControlName="profession" /></div>
    <button>Отправить</button>
  </form>
</div>

Директива [formGroup] отвечает за привязку формы.[formGroup]="myForm" — так происходит привязка нашей формы к верстке. Для привязки полей мы используем formControlName. Мы явно прописываем, какой инпут к какому полю в formGroup принадлежит. Все.

Что так просто? Да, так просто. Давайте добавим функцию для отправки и посмотрим, что нам выдаст нам консоль. Уже по знакомой схеме вешаем на форму submitForm(), а кнопке дает type="submit".

Должно выглядеть так:

Angular Forms
Angular Forms

Ну и конечно, в app.component.ts добавим функцию submitForm()

submitForm() {
  console.log(this.myForm.value);
}

А теперь откроем браузер и посмотрим, что же у нас там?

Reactive Forms Angular
Reactive Forms Angular

А у нас там все тоже самое, что и в шаблонных формах. То, какой подход вам использовать — решать вам. Еще про реактивные формы можно почитать на официальном сайте Angular: https://angular.io/guide/reactive-forms, а мы продолжаем.

Альтернативный подход с помощью FormBuilder

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

export class AppComponent {
  myForm!: FormGroup;
  constructor() {
  }
}

Теперь нам необходимо в конструктор передать FormBuilder и инициализировать форму.

export class AppComponent {
  myForm!: FormGroup;

  constructor(private FormBuilder: FormBuilder) {
    this.myForm = FormBuilder.group({
      name: [''],
      age: [''],
      profession: ['']
    });
  }

  submitForm() {
    console.log(this.myForm);
  }

Заглянем в браузер:

Angular 13: Формы и все что вы о них хотите знать

А там всё по старому. Опять же, какой способ создания форм использовать — выбирать вам. Я показал это исключительно для общеобразовательной базы. Сам я больше люблю FormGroup и FormControl, но на практике чаще использую шаблонные формы (NgForm).

Валидаторы форм в Angular

В ангуляре есть встроенная валидация форм, с полным списком вы можете ознакомиться на официальном сайте: https://angular.io/api/forms/Validators.

Реактивные формы

Давайте начнем с реактивных форм, для этого перейдем в наш app.component.ts и отредактируем нашу форму.

myForm: FormGroup = new FormGroup({
  name: new FormControl('', Validators.required),
  age: new FormControl(''),
  profession: new FormControl(''),
});

Мы добавили свойство — Validators.required, говорит на о том, что поле name не может быть пустым. Давайте перейдем в браузер и проверим, что же будет, если мы не заполним поле и отправим форму.

Angular 13: Формы и все что вы о них хотите знать

Нас интересует поле status. Как мы видим оно INVALID, но форма отправилась, да. Потому что мы никак не реагируем на это. Мы можем либо запретить отправку, либо еще как-то обработать это в самой функции. В данном примере мы просто заблокируем кнопку отправки.

Перейдем в нашу верстку и изменим нашу кнопку отправки.

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

Мы знаем, что если просто прописать HTML атрибут disabled — это отключит кнопку. Однако в ангуляре это можно делать по условию. Мы словно «говорим»: если условие верно, что форма в статусе invalid, тогда отключи кнопку. Глянем HTML:

Angular 13: Формы и все что вы о них хотите знать

Как мы видим, кнопка наша отключена. Давайте продолжим. Как я уже говорил у ангуляра есть свои встроенные валидаторы и их можно комбинировать, для этого их необходимо объединить в квадратные скобки [].

name: new FormControl('', [Validators.required, Validators.minLength(3)]),

Теперь мы говорим, что поле должно быть не пустым и иметь минимум 3 символа.

Angular 13: Формы и все что вы о них хотите знать

Кнопка всё еще заблокирована, потому что я ввел только 2 символа в поле name.

Для регулярных выражений есть Validators.pattern, мы можем переделать нашу строку с name. Вместо встроенного валидатора на минимальную длину, мы можем написать свой, где также уточним, какие символы хотим видеть в поле.

name: new FormControl('', [Validators.required, Validators.pattern('^[a-zA-Z]{3,}$')]),
Angular 13: Формы и все что вы о них хотите знать

Не работает, потому что мы разрешили только латинские буквы.

Angular 13: Формы и все что вы о них хотите знать

Теперь всё окей.

Шаблонные формы

С реактивными формами понятно, а как использовать с шаблонными формами? Подсказка в названии — в самих шаблонах и использовать.

Очистим компонент app.component.ts и возьмем нашу стару верстку

app.component.ts:

export class AppComponent {
  submitForm() {
  }
}

app.component.html:

<div>
  <form #myForm="ngForm" (ngSubmit)="submitForm()">
    <div>Имя: <input name="name" id="name" type="text" ngModel /></div>
    <div>Возраст: <input name="age" id="age" type="text" ngModel /></div>
    <div>Профессия: <input name="profession" id="profession" type="text" ngModel /></div>
    <button type="submit">Отправить</button>
  </form>
</div>

А теперь возьмем наш инпут и добавим туда свойство: required.

<input name="name" id="name" type="text" ngModel required/>

А на кнопку повесим [disabled]="myForm.invalid"

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

Браузер:

Angular 13: Формы и все что вы о них хотите знать

А если регулярное выражение добавить? Все точно также, только не надо писать слово Validators.

<div>Имя: <input name="name" id="name" type="text" pattern="^[a-zA-Z]{3,}$" ngModel required /></div>

Если валидаторов нужно несколько, то пишем их через пробел. Проверяем браузер:

Angular 13: Формы и все что вы о них хотите знать

Регулярка работает! Все супер!

Заключение

Ну вот и все! На самом деле, после написания понял, что заголовок слишком громкий и рассказал я далеко не все. Но этих начальных знаний будет достаточно, чтобы двигаться уже самому. Если будут какие-либо вопросы, задавайте здесь или на YouTube.

Всем спасибо и до новых встреч!

Булкин Денис.

Оцените статью
GeekБлог
Добавить комментарий

  1. Maxim

    Спасибо за статью, очень доходчиво пишешь. С удовольствием прошел оба видеоролика. Привет от разработчика Angular.

    Ответить
    1. DeniDenx автор

      спасибо!)

      Ответить