Angular: работа с формами
В ангуляре есть 2 подхода к созданию форм:
Template-Driven – все настройки формы (валидаторы, группировка и т.д.) прописываются внутри html-шаблона, а в классе происходит только обработка полученного значения. Для их работы в app.modules.ts подключаются FromsModule.
Reactive – форма создается програмно, т.е. все настройки ее прописываются в классе и потом синхронизируется с DOM, где находится практически «голая форма». Для их работы в app.modules.ts подключаются ReactiveFormsModule.
Получение данных с формы при отправке (нажатии submit)
Вариант №1 (передача локальной ссылки внутрь функции с шаблона)
Html шаблон:
<form (ngSubmit)="onSubmit(testForm)" #testForm="ngForm"> <input type="text" ngModel name="username"> <input type="text" ngModel name="email"> <button type="submit">Test Submit</button> </form>
Класс компонента:
onSubmit(data: NgForm) { console.log(data); }
Вариант №2 (применение @ViewChild вместо передачи ссылки через параметр)
Это более предпочтительный метод, хотя возвращает точно тот же объект, а синтаксис выглядит даже сложнее.
<form (ngSubmit)="onSubmit()" #testForm="ngForm"> <input type="text" ngModel name="username"> <input type="text" ngModel name="email"> <button type="submit">Test Submit</button> </form>
Класс компонента:
export class MoveDetalisComponent implements OnInit { @ViewChild('testForm') someNewNameFrom: NgForm | null = null; constructor(private testService: TestService) {} onSubmit() { console.log(this.someNewNameFrom); } ....
Валидация формы
Простая валидация с отключением кнопки и присвоением стилей невалидным инпутам
1. Добавляем условия валидации к инпутам, такие как required и email, делаем кнопку неактивной, когда эти условия false, а также выводим span с предупреждением, когда емейл невалидный:
<form (ngSubmit)="onSubmit()" #testForm="ngForm"> <input type="text" ngModel name="username" required> <input type="text" ngModel name="email" email="ng-modal" required span> <span *ngIf="email.invalid && email.touched">Enter valid email!</div> <button [disabled]="!testForm.valid" type="submit">Test Submit</button> </form>
Условия, которые вешаются на кнопку либо блок, всплывающие при невалидности, можно комбинировать операторами &&
или ||
, как видно в примере с блоком выше.
2. Присваиваем стили к компонентам, которые не прошли валидацю и уже были touched (это можно благодаря тому, что инпутам Агуляр добавляет определенные стили, зависимо от происходящих с ними действиий):
input.ng-invalid.ng-touched { border: 3px solid red; }
Байндинг и значение по умолчанию для инпута
Есть 3 варианта в Ангуляре:
1) Без байндинга – как во всех примерах выше, когда байндинга и значения по умолчанию для инпута нет совсем.
2) Односторонний байндинг, который позволяет задать значение инпуту по умолчанию. По сути это «разовая статика»:
<input type="text" ngModel name="email"[ngModel]="emailDefault">
3) Двусторонний байндинг – добавляем просто круглые скобки внутри [ngModel] и получаем полный контроль над инпутом:
<input type="text" ngModel name="email"[(ngModel)]="emailDefault">
Группировка элементов формы
Внутри формы ее несколько элементов (либо все) для удобства валидации и получения данных можно групировть при помощи директивы ngModelGroup
<form (ngSubmit)="onSubmit()" #testForm="ngForm"> <div class="form-group" ngModelGroup="userMainData"> <input type="text" ngModel name="username"> <input type="text" ngModel name="firstname"> <input type="text" ngModel name="lastname"> </div> <button [disabled]="!testForm.valid" type="submit">Test Submit</button> <div class="form-group" ngModelGroup="userContactData"> <input type="number" ngModel name="phone"> <input type="text" ngModel name="email"> </div> </form>
Теперь при отправке формы в свойстве value будет храниться объект не со значениями инпутов, как было ранее, а эти значения будут внутри двух других объектов – userContactData и userMainData (названия наших групп).
Валидация группы
Кроме того, в DOMe к нашим группам применяются классы для валидации, как для инпутов, благодаря чему можно валидировать целую группу (если какой-то из инпутов невалидны, то что-то выводить).
В примере ниже для группы добавляем локальную переменную #userMainData="ngModelGroup"
, и потом выводим блок при ее невалидности аналогично инпутам в примерах выше.
<form (ngSubmit)="onSubmit()" #testForm="ngForm"> <div class="form-group" ngModelGroup="userMainData" #userMainData="ngModelGroup"> <input type="text" ngModel name="username" required> <input type="text" ngModel name="firstname" required> <input type="text" ngModel name="lastname" required> </div> <div *ngIf="!userMainData.valid && userMainData.touched" class="error">User Data is invalid</div> <button [disabled]="!testForm.valid" type="submit">Test Submit</button> <div class="form-group" ngModelGroup="userContactData"> <input type="number" ngModel name="phone"> <input type="text" ngModel name="email"> </div> </form>
Другие типы инпутов
Select — option
Допустим, нам надо вывести выпадающий список с вопросами и textarea для ввода ответа:
<form (ngSubmit)="onSubmit()" #testForm="ngForm"> <div class="form-group"> <label for="secret">Secret Questions</label> <select id="secret" [ngModel]="defaultQuestion" name="secret"> <option value="pet">Your first Pet?</option> <option value="teacher">Your first teacher?</option> </select> <textarea name="questionAnswer" class="form-control" [(ngModel)]="answer"></textarea> </div> <button [disabled]="!testForm.valid" type="submit">Test Submit</button> </form>
В классе просто задаем 2 свойства – вопрос по умолчанию (должен совпадать из value одной из option) и переменную для байндинга с полем для ответа:
defaultQuestion: string = 'pet'; answer: string = '';
Radio buttons
На самом деле здесь все практически, как для обычного инпута.
Например, мы имеем массив genders в классе компонента:
genders = ['male', 'female', 'others'];
Выведем на его основе в шаблоне radi кнопки внутри формы:
<form (ngSubmit)="onSubmit()" #testForm="ngForm"> <div class="form-group" ngModelGroup="userMainData" #userMainData="ngModelGroup"> <input type="text" ngModel name="username" required> <input type="text" ngModel name="firstname" required> <input type="text" ngModel name="lastname" required> </div> <div *ngIf="!userMainData.valid && userMainData.touched" class="error">User Data is invalid</div> <button [disabled]="!testForm.valid" type="submit">Test Submit</button> <div class="radio" *ngFor="let gender of genders"> <label> <input type="radio" name="gender" ngModel [value]="gender"> {{gender}} </label> </div> </form>
Все, теперь при отправке формы получить значение можно в NgForm.value.gender
Автозаполнение формы по клику
Например, есть у нас вот такой шаблон:
<form (ngSubmit)="onSubmit()" #testForm="ngForm"> <div ngModelGroup="userMainData" #userMainData="ngModelGroup"> <input type="text" ngModel name="username" required> <input type="text" ngModel name="firstname" required> <input type="text" ngModel name="lastname" required> </div> <button [disabled]="!testForm.valid" type="submit">Test Submit</button> </form>
В него мы при помощи метода patchValue у нашей подключенной в классе формы передаем объект данных, аналогичный получаемому при отправке:
... export class MoveDetalisComponent implements OnInit { @ViewChild('testForm') someNewNameFrom: NgForm | null = null; constructor(private testService: TestService) {} onSubmit() { this.someNewNameFrom?.form.patchValue({ userMainData: { firstname: 'Ivan', lastname: 'Ivanov', username: 'iviv235', }, }); } ...
Есть и другой способ, но менее предпочительнй (речь о this.nameForm.setValue, куда таким же образом передается аналогичный объект).
Очистка формы
Для сбрасывания (перезагрузки) формы после отправки и не только используется метод reset(form) у объявленой формы:
@ViewChild('testForm') someNewNameFrom: NgForm | null = null; this.someNewNameFrom.reset();