В Angular прежде чем использовать формы в компонентах, надо импортировать в главном модуле AppModule модуль FormsModule, который позволяет работать с формами:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [ BrowserModule, FormsModule],
declarations: [ AppComponent],
bootstrap: [ AppComponent ]
})
export class AppModule { }Кроме того, в файле конфигурации приложения package.json среди списка используемых зависимостей должен быть указан пакет "angular/forms":
{
"name": "helloapp",
"version": "1.0.0",
"scripts": {
"start": "tsc && concurrently \"tsc -w\" \"lite-server\" ",
"lite": "lite-server",
"tsc": "tsc",
"tsc:w": "tsc -w"
},
"dependencies": {
"@angular/forms": "~4.0.0",
// остальные пакеты
},
"devDependencies": {
// остальные пакеты
}
}При работе с формами ключевым моментом является использование директивы NgModel. Эта директива с помощью переданной модели создает объект FormControl и привязывает эту модель к созданному элементу формы. Объект FormControl отслеживает значение модели, а также отвечает за валидацию этого значения и взаимодействие с пользователем.
Данная директива принимает переданную ей модель в качестве входного свойства. Причем мы можем использовать как однонаправленную, так и двунаправленную привязку.
//one-way
<input name="title" [ngModel]="title" />
//two-way
<input name="title" [(ngModel)]="title" />//Phone.component
import {Component} from '@angular/core';
@Component({
selector: 'phone',
templateUrl: "app/phone.component.html"
})
export class Phone {
public phones:any[] = [];
public companies:string[] = [
"Apple",
"Huawei",
"Xiaomi",
"Samsung",
"LG",
"Motorola",
"Alcatel"
];
public addPhone(title:string, price:number, company:string) {
this.phones.push({title, price, company});
}
}//phone.component.html
<label>Название модели</label>
<input name="title" [(ngModel)]="title"/>
<label>Цена</label>
<input
type="number"
name="price"
[(ngModel)]="price"/>
<label>Производитель</label>
<select name="company" [(ngModel)]="company">
<option *ngFor="let comp of companies" [value]="comp">
{{comp}}
</option>
</select>
<button
(click)="addPhone(title, price, company)"
>Добавить </button>
<h3>Добавленные элементы</h3>
<ul *ngFor="let p of phones">
<li>{{p.title}} ({{p.company}}) - {{p.price}}</li>
</ul>Local example (phone.component.ts)
Кроме создания привязки директива ngModel позволяет определить объект NgModel, который будет связан с определенным элементом ввода.
//phone2.component.ts
import { Component} from '@angular/core';
import { NgModel} from '@angular/forms';
@Component({
selector: 'phone2',
templateUrl: "app/phone2.component.html"
})
export class Phone2 {
public phone: any ={
title: "",
price: 0,
company: "Samsung"
};
public companies: string[] = [
"Apple",
"Huawei",
"Xiaomi",
"Samsung",
"LG",
"Motorola",
"Alcatel"
];
public addPhone(title:NgModel,
price: NgModel,
comp: NgModel){
console.log(title);
console.log(price);
console.log(comp);
}
}//phone2.component.html
<label>Название модели</label>
<input name="title"
[(ngModel)]="phone.title" #phoneTitle="ngModel"/>
<label>Цена</label>
<input type="number" name="price"
[(ngModel)]="phone.price" #phonePrice="ngModel"/>
<label>Производитель</label>
<select name="company"
[(ngModel)]="phone.company" #phoneCompany="ngModel">
<option *ngFor="let comp of companies" [value]="comp">
{{comp}}
</option>
</select>
<button (click)="addPhone(phoneTitle, phonePrice, phoneCompany)">
Добавить
</button>
<div>
<p>{{phoneTitle.name}} : {{phoneTitle.model}}</p>
<p>{{phonePrice.name}} : {{phonePrice.model}}</p>
<p>{{phoneCompany.name}} : {{phoneCompany.model}}</p>
</div>Local example (phone2.component.ts)
//template
<input name="title"
[(ngModel)]="phone.title" #phoneTitle="ngModel"
(change)="onTitleChange()" />
<label>Цена</label>
<input type="number" name="price"
[(ngModel)]="phone.price" #phonePrice="ngModel"
(ngModelChange)="onModelChange()"/>//component
public onTitleChange():void {
if (this.phone.title) {
console.log("title", this.phone.title);
}
}
public onModelChange(): void {
if (this.phone.price) {
console.log("price", this.phone.price);
}
}Local example (phone2)!!!
Применение директивы ngModel не только устанавливает привязку данных, но и позволяет отслеживать состояние элемента ввода. Для установки состояния Angular применяет к элементам ввода специальные классы CSS:
Если элемент ввода еще не получал фокус, то устанавливается класс ng-untouched. Если же поле ввода уже получало фокус, то к нему применяется класс ng-touched. При этом получение фокуса не обязательно должно сопровождаться изменением значения в этом поле.
Если первоначальное значение в поле ввода было изменено, то устанавливается класс ng-dirty. Если же значение не изменялось с момента загрузки страницы, то к элементу ввода применяется класс ng-pristine
Если значение в поле ввода корректно, то применяется класс ng-valid. Если же значение некорректно, то применяется класс ng-invalid
<input class="form-control" name="title" [(ngModel)]="title" />
//generated
<input class="form-control ng-untouched ng-pristine ng-valid" name="title" ng-reflect-name="title" />В Angular 2 мы можем использовать валидацию HTML5, которая применяется в виде атрибутов:
required: требует обязательного ввода значения
pattern: задает регулярное выражение, которому должны соответствовать вводимые данные
//template
<input name="title"
[(ngModel)]="phone.title"
#phoneTitle="ngModel"
(change)="onTitleChange()"
required/>
<span *ngIf="phoneTitle.invalid && phoneTitle.touched"
class="alert alert-danger">
Title is required
</span>
<label>Цена</label>
<input type="number"
name="price"
[(ngModel)]="phone.price"
#phonePrice="ngModel"
(ngModelChange)="onModelChange()"
required
pattern="[0-9]{3}"/>
<span *ngIf="phonePrice.invalid && phonePrice.touched"
class="alert alert-danger">
Price is invalid
</span>Как правило, при работе с формами все элементы ввода не определяются сами по себе, а помещаются в стандартный элемент формы - <form></form>. Применение данного элемента позволяет управлять всеми элемента ввода вцелом как одной общей формой.
Непосредственно в Angular для работы с формой определена специальная директива NgForm. Она создает объект FormGroup и привязывает его к форме, что позволяет отслеживать состояние формы, управлять ее валидацией.
//user-from template
<form #myForm="ngForm" novalidate>
<label>Имя</label>
<input
class="form-control"
name="name"
[(ngModel)]="name"
required/>
<label>Email</label>
<input
class="form-control"
name="email" ngModel
required
pattern="[a-zA-Z_]+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}"/>
<label>Телефон</label>
<input
class="form-control"
name="phone"
ngModel
required pattern="[0-9]{10}"/>
<button
[disabled]="myForm.invalid"
class="btn btn-default"
(click)="submit(myForm)">Добавить
</button>
</form>
<div>Имя: {{myForm.value.name}}</div>
<div>Email: {{myForm.value.email}}</div>//user-from component
import { Component} from '@angular/core';
import { NgForm} from '@angular/forms';
@Component({
selector: 'user-form',
styleUrls: ["app/user.component.css"],
templateUrl: "app/user.component.html"
})
export class UserFormComponent {
public submit(form: NgForm){
console.log(form);
}
}В прошлых слайдах был подход Template-Driven, который концентрировался вокруг шаблона компонента: для работы с формой и ее элементами в шаблоне компонента к элементам html применялись директивы NgModel и NgForm, правила валидации задавались в тегах элементов с помощью атрибутов required и pattern. Но есть альтернативный подход - Data-Driven. Рассмотрим, в чем он заключается.
При подходе Data-Driven для формы создается набор объектов FormGroup и FormControl. Сама форма и ее подсекции представляют класс FormGroup, а отдельные элементы ввода - класс FormControl
<form [formGroup]="myForm" novalidate (ngSubmit)="submit()">
<label>Имя</label>
<input name="name" formControlName="userName"/>
<div class="alert alert-danger"
*ngIf="myForm.controls['userName'].invalid && myForm.controls['userName'].touched">
Не указано имя
</div>
<label>Email</label>
<input name="email" formControlName="userEmail"/>
<div class="alert alert-danger"
*ngIf="myForm.controls['userEmail'].invalid && myForm.controls['userEmail'].touched">
Некорректный email
</div>
<label>Телефон</label>
<input name="phone" formControlName="userPhone"/>
<button [disabled]="myForm.invalid">Отправить</button>
</form>import { Component} from '@angular/core';
import { FormGroup, FormControl, Validators} from '@angular/forms';
@Component({
selector: 'user-form-2',
styleUrls: ["app/user.component.css"],
templateUrl: "app/user2.component.html"
})
export class UserFrom2Component {
public myForm : FormGroup;
constructor(){
this.myForm = new FormGroup({
"userName": new FormControl("Tom", Validators.required),
"userEmail": new FormControl("", [
Validators.required,
Validators.pattern("[a-zA-Z_]+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}")
]),
"userPhone": new FormControl()
});
}
public submit(){
console.log(this.myForm);
}
}По сути валидатор представляет обычный метод - в данном случае метод userNameValidator. В качестве параметра он принимает элемент формы, к которому этот валидатор применяется, а на выходе возвращает объект, где ключ - строка, а значение равно true.
В данном случае проверяем, если значение равно строке "нет", то возвращаем объект {"userName": true}. Значение true указывает, что элемент формы не прошел валидацию. Если же все нормально, то возвращаем null.
export class UserFrom2Component {
public myForm : FormGroup;
constructor(){
this.myForm = new FormGroup({
"userName": new FormControl("Tom", [Validators.required, this.userNameValidator]),
"userEmail": new FormControl("", [
Validators.required,
Validators.pattern("[a-zA-Z_]+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}")
]),
"userPhone": new FormControl()
});
}
public submit(){
console.log(this.myForm);
}
private userNameValidator(control: FormControl): {[s:string]:boolean}{
if(control.value==="нет"){
return {"userName": true};
}
return null;
}<form [formGroup]="myForm" novalidate (ngSubmit)="submit()">
<label>Имя</label>
<input class="form-control" name="name" formControlName="userName"/>
<div class="alert alert-danger"
*ngIf="myForm.controls['userName'].invalid && myForm.controls['userName'].touched">
Не указано имя
</div>
<label>Email</label>
<input class="form-control" name="email" formControlName="userEmail"/>
<div class="alert alert-danger"
*ngIf="myForm.controls['userEmail'].invalid && myForm.controls['userEmail'].touched">
Некорректный email
</div>
<div formArrayName="phones">
<div class="form-group"
*ngFor="let phone of myForm.controls['phones'].controls; let i = index">
<label>Телефон</label>
<input class="form-control" formControlName="{{i}}"/>
</div>
</div>
<button class="btn btn-default" (click)="addPhone()">
Добавить телефон
</button>
<button class="btn btn-default" [disabled]="myForm.invalid">
Отправить
</button>
</form>Некоторые элементы на форме могут относиться к одному и тому же признаку. Например, в анкете пользователя могут попросить указать номера телефоно, которыми он владеет. Их может быть несколько, но они будут представлять один и тот же признак - "номера телефонов". То есть логично было бы объединить все поля для ввода номеров телефонов в массив. И в Angular 2 мы легко можем реализовать подобную возможность с помощью класса FormArray.
export class FormArrayComponent {
public myForm:FormGroup;
constructor() {
this.myForm = new FormGroup({
"userName": new FormControl("Tom", [Validators.required]),
"userEmail": new FormControl("", [
Validators.required,
Validators.pattern("[a-zA-Z_]+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}")
]),
"phones": new FormArray([
new FormControl("+375", Validators.required)
])
});
}
public addPhone() {
(<FormArray>this.myForm.controls["phones"]).push(
new FormControl("+375", Validators.required)
);
}
public submit() {
console.log(this.myForm);
}
}Local example: form-array.componetn.ts
FormBuilder передается в качестве сервиса в конструктор. С помощью метода group() создается объект FormGroup. Каждый элемент передается в форму в виде обычного массива значений
export class FormBuildComponent {
private formBuilder: FormBuilder;
public myForm : FormGroup;
constructor(formBuilder: FormBuilder){
this.formBuilder = formBuilder;
this.myForm = formBuilder.group({
"userName": ["Tom", [Validators.required]],
"userEmail": ["", [
Validators.required,
Validators.pattern("[a-zA-Z_]+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}")
]],
"phones": formBuilder.array([
["+375", Validators.required]
])
});
}
public addPhone(){
(<FormArray>this.myForm.controls["phones"]).push(
new FormControl("+375", Validators.required)
);
}
public submit(){
console.log(this.myForm);
}
}