Приветствую Вас, любители 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 всегда):
Можем переходить в папку нашего проекта и открывать редактор кода. Готовая, чистая структура нашего проекта выглядит так:
Я не буду вдаваться в подробности о том, какой файл за что отвечает (об этом есть куча видео, может даже когда-то и я запишу). Возможно, что я буду делать это по ходу написания кода. Сейчас нам нужен файл app.component.html
, который находится в папке src/app
, мы можем полностью очистить его.
Запустим наш проект командой в терминале:
ng serve
Если вы увидели следующий лог:
Тогда все супер! В целом все готово и можно начинать! Ваш сервер работает и доступен в браузере по адресу: http://localhost:4200/
Формы в Angular, начало начал
Алерт! Мальчики и девочки, по ходу статьи я буду оставлять ссылки на официальные документации. Пожалуйста, в первую очередь обращайтесь к ним. Мое повествование — это лишь интерпретация моего видения. Я человек, а следовательно, я могу ошибаться.
Официальная документация Angular: Введение в формы. https://angular.io/guide/forms-overview
Формы — это то, что мы используем повседневно. Как разработчик или как пользователь, не столь важно. Мы с ними сталкиваемся везде, на сайтах и в приложениях, при регистрации, при введении каких-либо данных. В общем, формы — это то, с чем нам часто приходится сталкиваться каждый день.
Формы в Angular — бывают двух типов. Реактивные формы и формы на основе шаблонов. Шаблонные формы используют модуль — FormsModule, а реактивные — ReactiveFormsModule.
В чем же их отличие? Ну, во-первых, шаблонные формы — асинхронные, в то время как реактивные — синхронные. Во-вторых, большая часть логики шаблонных модулей написала в самом HTML (app.component.html
), в то время, как логика реактивных форм прописана в компоненте (app.component.ts
).
Как шаблонные, так и реактивные формы, наследуются от следующих базовых классов (они у них общие).
FormControl
— FormControl отслеживает значение каждого элемента формы отдельно.FormGroup
— FormGroup отслеживает целиком группу состоящую из контроллеров (FormControl).FormArray
— FormArray отслеживает массив состоящий из групп контроллеров (FormGroup, FormControl)ControlValueAccessor
— создает «мост» между FormControl и элементами DOM.
На самом деле, все не так страшно, как может показаться на первый взгляд. Сейчас перейдем к практике и вы увидите, что это очень легко и просто! Начнем с шаблонных форм.
Шаблонные формы в Angular
Шаблонные формы используют неявную модель присваивания, в отличии от реактивных форм. Сейчас дальше поймете. Для начала импортируем модуль форм. Для этого нам необходимо в файле app.module.ts
в разделе imports
добавить FormsModule
и он автоматически импортируется. Если этого не произошло, добавьте импорт вручную.
import { FormsModule } from '@angular/forms';
Ваш файл app.module.ts
должен выглядеть так:
Теперь перейдем в файл app.component.html
и создадим такую верстку:
<div>
<input type="text" [(ngModel)]="name">
</div>
Здесь ключевое [(ngModel)]="name"
, ngModel — отвечает за привязку, а name — это обычная переменная, которую мы сейчас создадим в файле app.component.ts
:
name = 'Denis';
Откроем браузер по адресу localhost:4200
и проверим, что все работает:
Супер, привязка сработала! Теперь вводимые данные хранятся в переменной name
. Я это показал, чтоб вы просто понимали как происходит привязка данных вне формы (причем, привязка двухсторонняя).
Чтобы это проверить, в app.component.html
в любое место добавьте вывод нашей переменной {{ name }}
, начните изменять данные и вы увидите результат.
Код должен выглядеть примерно так:
<div><input [(ngModel)]="name" />{{ name }}</div>
А отображается так:
Давайте теперь создадим полноценную форму, с именем, профессией и возрастом. Для этого перейдем в файл 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';
Теперь перейдем в браузер, откроем консоль и попробуем заполнить поля и нажать кнопку отправки нашей формы. Посмотрим, что произойдет.
Форма отлично отправляется, все супер. Но! Мы не получаем значения наших полей, на скриншоте это объект 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>
Попробуем еще раз? Снова перейдем в браузер, заполним форму, откроем консоль и нажмем отправить.
Ну вот, уже другое дело. Мы получаем данные о том, что ввел пользователь. Неявно, «под капотом», каждому из полей присваивается FormControl
, если это вам ни о чем не говорит, подождите, скоро дойдем до реактивных форм, там мы их будем прописывать сами!
Для того чтобы получить доступ к каждому из полей, мы можем в нашей функции обратиться к этим полям по такому шаблону: имя_нашей_формы.value.интересующее_нас_поле
. Давайте покажу нагляднее, изменим немного нашу функцию submitForm
submitForm(form: NgForm) {
console.log(form.value.name);
console.log(form.value.age);
console.log(form.value.profession);
}
Откроем браузер и проделаем все то, что делали выше:
Если вы хотите «на месте» отображать эти же данные прямо в шаблоне, вы можете вставить в ваш код (app.component.html
):
<div>
{{myForm.value.name}}
{{myForm.value.age}}
{{myForm.value.profession}}
</div>
Начните менять значения в инпутах и вы увидите, как моментально меняются и отображаемые данные. Для этого вам даже не надо нажимать кнопку отправить, данные меняются «на лету».
Что если мы хотим очистить нашу форму после отправки? Для этого в нашу функцию 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!: NgForm
— form — это мы даем новое название нашей форме в компоненте, то, как мы к ней будем обращаться (название может быть любое). Ну и NgForm — определяем тип нашей формы. (Это уже относится к TypeScript, можете написать any пока что (что не рекомендуется конечно, но если вы не знакомы с typescript и только изучаете, то ок)).
Ну вот, теперь мы следим за нашей формой и получаем все данные о ней в переменную form. Давайте очистим нашу функцию submitForm()
и добавим следующий код.
submitForm() {
console.log(this.form.value);
}
Да, теперь мы можем просто обращаться к нашей форме через this.form
. Результат будет такой же. Глянем в наш браузер:
Супер! Все работает! На этом мы пока остановимся и перейдем к реактивным формам, давайте же посмотрим, как реализовать все это с помощью реактивных форм.
Реактивные формы в Angular
Во-первых, давайте очистим все то, что мы писали до этого. Сделаем наши компоненты пустыми и начнем с нуля. Очищаем компонент app.component.ts
и оставляем только это:
А в файле app.component.html
вообще удалим все. Чтобы все было наглядно и с нуля. Теперь у нас первоначальный вид нашего приложения. Первым делом, нам необходимо импортировать ReactiveFormsModule
. Переходим в файл app.module.ts
и импортируем его. Должно выглядеть так:
Переходим к нашей верстке, открываем наш файл 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"
.
Должно выглядеть так:
Ну и конечно, в app.component.ts
добавим функцию submitForm()
submitForm() {
console.log(this.myForm.value);
}
А теперь откроем браузер и посмотрим, что же у нас там?
А у нас там все тоже самое, что и в шаблонных формах. То, какой подход вам использовать — решать вам. Еще про реактивные формы можно почитать на официальном сайте 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);
}
Заглянем в браузер:
А там всё по старому. Опять же, какой способ создания форм использовать — выбирать вам. Я показал это исключительно для общеобразовательной базы. Сам я больше люблю 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
не может быть пустым. Давайте перейдем в браузер и проверим, что же будет, если мы не заполним поле и отправим форму.
Нас интересует поле status. Как мы видим оно INVALID, но форма отправилась, да. Потому что мы никак не реагируем на это. Мы можем либо запретить отправку, либо еще как-то обработать это в самой функции. В данном примере мы просто заблокируем кнопку отправки.
Перейдем в нашу верстку и изменим нашу кнопку отправки.
<button [disabled]="myForm.invalid" type="submit">Отправить</button>
Мы знаем, что если просто прописать HTML атрибут disabled — это отключит кнопку. Однако в ангуляре это можно делать по условию. Мы словно «говорим»: если условие верно, что форма в статусе invalid, тогда отключи кнопку. Глянем HTML:
Как мы видим, кнопка наша отключена. Давайте продолжим. Как я уже говорил у ангуляра есть свои встроенные валидаторы и их можно комбинировать, для этого их необходимо объединить в квадратные скобки [].
name: new FormControl('', [Validators.required, Validators.minLength(3)]),
Теперь мы говорим, что поле должно быть не пустым и иметь минимум 3 символа.
Кнопка всё еще заблокирована, потому что я ввел только 2 символа в поле name.
Для регулярных выражений есть Validators.pattern
, мы можем переделать нашу строку с name
. Вместо встроенного валидатора на минимальную длину, мы можем написать свой, где также уточним, какие символы хотим видеть в поле.
name: new FormControl('', [Validators.required, Validators.pattern('^[a-zA-Z]{3,}$')]),
Не работает, потому что мы разрешили только латинские буквы.
Теперь всё окей.
Шаблонные формы
С реактивными формами понятно, а как использовать с шаблонными формами? Подсказка в названии — в самих шаблонах и использовать.
Очистим компонент 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>
Браузер:
А если регулярное выражение добавить? Все точно также, только не надо писать слово Validators.
<div>Имя: <input name="name" id="name" type="text" pattern="^[a-zA-Z]{3,}$" ngModel required /></div>
Если валидаторов нужно несколько, то пишем их через пробел. Проверяем браузер:
Регулярка работает! Все супер!
Заключение
Ну вот и все! На самом деле, после написания понял, что заголовок слишком громкий и рассказал я далеко не все. Но этих начальных знаний будет достаточно, чтобы двигаться уже самому. Если будут какие-либо вопросы, задавайте здесь или на YouTube.
Всем спасибо и до новых встреч!
Булкин Денис.
Спасибо за статью, очень доходчиво пишешь. С удовольствием прошел оба видеоролика. Привет от разработчика Angular.
спасибо!)