Angular的反应形式模块是创建和管理复杂形式逻辑的强大工具。它使应用程序能够以清晰,可预测和可扩展的方式响应用户输入。反应性形式的核心特征之一是其基于可观察的API,它使我们能够构建反应性UI,以随着时间的流逝而反应变化。
角反应性形式由Form Control
,Form Group
和Form Array
等构件组成。这些构建块允许您创建和组织形式控件,组相关输入,并动态处理多个输入。
反应性形式的核心概念
Angular的反应性形式是围绕可观察到的流进行设计的,为开发人员提供了完全控制的,以在任何给定时刻管理形式的状态。这些形式的结构以模型驱动的方法为基础,受到基础数据模型的影响,而不是用户界面(UI)。
本文将指导您介绍角反应式形式的关键特征,并提供为每个方面提供角组件代码。
探索构建块
1。表格组:基础
FormGroup
是FormControl
对象的集合。它将每个子FormControl
的值汇总到一个对象中,每个控件名称用作密钥。
下面是使用FormGroup
的组件的一个示例:
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'app-profile-editor',
template: `
<form [formGroup]="profileForm">
<label>
First Name:
<input type="text" formControlName="firstName">
</label>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
</form>
`,
})
export class ProfileEditorComponent {
profileForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
});
}
2。表单数组:管理动态输入
FormArray
提供了FormGroup
的替代方法,用于管理任何数量的未命名控件。与FormGroup固定的命名控件不同,FormArrays通常管理动态添加的控件。
这是一个带有FormArray
的组件:
import { Component } from '@angular/core';
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector: 'app-profile-editor',
template: `
<form [formGroup]="profileForm">
<div formArrayName="aliases">
<div *ngFor="let alias of aliases.controls; let i=index">
<input [formControlName]="i">
</div>
</div>
<button (click)="addAlias()">Add Alias</button>
</form>
`,
})
export class ProfileEditorComponent {
profileForm: FormGroup;
constructor(private fb: FormBuilder) {
this.profileForm = this.fb.group({
aliases: this.fb.array([
this.fb.control('')
])
});
}
get aliases() {
return this.profileForm.get('aliases') as FormArray;
}
addAlias() {
this.aliases.push(this.fb.control(''));
}
}
3。表单验证:确保输入质量
Angular提供了一组内置验证器,您还可以编写自己的自定义验证器。验证器是处理FormControl
或控件集合并返回错误或null的功能。
这是您将验证添加到FormControl
的方式:
import { Component } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-example',
template: `
<input [formControl]="name" placeholder="Enter name">
<p *ngIf="name.invalid && (name.dirty || name.touched)" class="error">
Name is required.
</p>
`,
})
export class ExampleComponent {
name = new FormControl('', Validators.required);
}
4。利用反应性形式的可观察物
每个FormControl
,FormGroup
和FormArray
实例都有一个可观察到的valueChanges
。您可以订阅可观察到的时,只要对控件的值变化就可以做出反应。
this.profileForm.get('firstName').valueChanges.subscribe(value => {
console.log('First name has changed to:', value);
});
可观察的形成了角反应性形式的骨干。它们提供了处理各种场景的机制,包括处理用户事件,管理异步操作和维持状态。
使用可观察形式的可观察物的主要优点之一是它们提供了诸如map
,filter
,debounceTime
等广泛的操作员的能力,从而简化了处理复杂的方案。
让我们实现一个反应性转换以监视对firstName
字段的更改:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { User } from './user.model';
import { UserService } from './user.service';
import { Subject } from 'rxjs';
import { debounceTime, filter, switchMap, takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-user-form',
template: `
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<input formControlName="firstName" placeholder="First Name">
<input formControlName="lastName" placeholder="Last Name">
<input formControlName="email" placeholder="Email">
<input formControlName="age" placeholder="Age">
<button type="submit">Submit</button>
</form>`
})
export class UserFormComponent implements OnInit, OnDestroy {
userForm = new FormGroup<User>({
firstName: new FormControl(''),
lastName: new FormControl(''),
email: new FormControl(''),
age: new FormControl(null),
});
userSearchResults = [];
private destroy$ = new Subject<void>();
constructor(private userService: UserService) {}
ngOnInit(): void {
this.userForm.get('firstName').valueChanges.pipe(
debounceTime(400),
filter(value => value.length > 2),
switchMap(value => this.userService.search(value)),
takeUntil(this.destroy$)
).subscribe(results => {
this.userSearchResults = results;
});
}
onSubmit(): void {
if (this.userForm.valid) {
this.userService.addUser(this.userForm.value);
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}
在此组件中,我们在ngOnInit
生命周期方法中订阅了形式的firstName
字段的更改。我们使用debounceTime
,filter
和switchMap
操作员来控制我们基于用户输入的频率。
我们还使用takeUntil
操作员在销毁组件时可观察到的valueChanges
取消订阅,以防止潜在的内存泄漏。
提交表单时,我们使用onSubmit
方法检查表单有效性,如果有效,我们将其调用带有表单值的userService.addUser
。
最佳实践
使用角反应性形式时,遵守某些最佳实践可能会导致更有效和可维护的代码:
- 不变性:将您的形式的价值视为不变。而不是修改现有值,而是创建一个新值。这种方法允许Angular有效跟踪更改并更新UI。
this.userForm.setValue({...this.userForm.value, firstName: 'New name'});
-
反应性转换:利用RXJS运算符,用于反应性变换值。这种做法可以使您的组件代码保持清洁,增强可读性,并以简单性促进复杂的转换。这是一个使用
map
操作员来转换输入流的示例:
this.userForm.get('age').valueChanges.pipe(
map(value => Math.max(0, value))
).subscribe(value => {
console.log(value);
});
- 基于组件的设计:将您的表格作为组件组成结构,每个组件都封装了您的表单的一部分。该组织方法有助于更可维护和有组织的代码库。
将您的表格分解为较小的,易于管理的组件。例如,为用户的个人信息创建一个不同的组件:
@Component({
selector: 'app-personal-info',
inputs: ['parentForm: formGroup'],
templateUrl: './personal-info.component.html'
})
export class PersonalInfoComponent {
@Input()
parentForm: FormGroup;
}
然后,以这样的父母形式使用它:
<form [formGroup]="userForm">
<app-personal-info [parentForm]="userForm"></app-personal-info>
<!-- Other form components -->
</form>
-
错误处理:使用
statusChanges
可观察到的表单或单个表单控件的验证状态。该技术允许在UI中反应地显示错误消息。
this.userForm.get('firstName').statusChanges.pipe(
filter(status => status === 'INVALID')
).subscribe(() => {
console.log('First name is invalid');
});
-
避免内存泄漏:时,请记住要在销毁组件时删除可观察的物品以防止内存泄漏。
takeUntil
操作员提供了一种简单的方法。
当组件被破坏时,请不要忘记从valueChanges
订阅:
private destroy$ = new Subject<void>();
ngOnInit(): void {
this.userForm.get('firstName').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(value => {
console.log(value);
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
-
异步验证:通过在验证器函数中返回
Promise
或Observable
来进行异步验证。这允许对服务器端资源的表单输入验证。
异步验证的一个示例,我们检查是否采取了username
:
this.userForm.get('username').setAsyncValidators(this.usernameValidator.validate.bind(this.usernameValidator));
UsernameValidator
服务可能看起来像这样:
@Injectable({ providedIn: 'root' })
export class UsernameValidator {
constructor(private userService: UserService) {}
validate(control: AbstractControl): Promise<ValidationErrors|null> | Observable<ValidationErrors|null> {
return this.userService.isUsernameTaken(control.value).pipe(
map(isTaken => isTaken ? { usernameTaken: true } : null),
catchError(() => null)
);
}
}
在此验证器服务中,进行了后端查询以检查是否获取用户名,如果是,则返回验证错误。
Angular模板中的反应性形式控制
Angular的反应性形式提供了一组指令,以在模板中链接形式控件。这是您可以链接它们的方法:
-
formControlname :指令将独立的
FormControl
实例链接到表单控制元素。 -
formGroupName :此指令将
FormGroup
实例链接到DOM元素。 -
formarrayname :指令将
FormArray
实例链接到dom元素。
FormArray
在Angular中是一个类,当您要创建一个动态形式时,可以允许用户添加或删除输入或输入组。当不知道输入的确切数量或一组输入时,此功能变得特别有用。
考虑一个表单方案,用户可以输入个人详细信息和多个地址(家庭,办公室等)。在这种情况下,地址可以表示为formgroup实例的formarray,每个formgroup代表一个地址。
这是如何在user-form.component.ts
中实现的证明:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms';
@Component({
selector: 'app-user-form',
templateUrl: './user-form.component.html',
})
export class UserFormComponent implements OnInit {
userForm: FormGroup;
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.userForm = this.fb.group({
personalDetails: this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
age: [''],
}),
addresses: this.fb.array([
this.createAddressFormGroup()
])
});
}
createAddressFormGroup(): FormGroup {
return this.fb.group({
street: ['', Validators.required],
city: ['', Validators.required],
state: ['', Validators.required],
zip: ['', Validators.required],
});
}
get addresses(): FormArray {
return this.userForm.get('addresses') as FormArray;
}
addAddress() {
this.addresses.push(this.createAddressFormGroup());
}
removeAddress(index: number) {
this.addresses.removeAt(index);
}
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);
} else {
alert("Form is invalid, please review your information");
}
}
}
在上面的示例中,addresses
是一个FormArray
,从一个地址的一个FormGroup
实例开始。 createAddressFormGroup
方法构造了一个地址的FormGroup
。地址getter方法返回地址FormArray
。 addAddress
和removeAddress
方法用于添加和删除地址中的地址的FormGroup
实例。
现在,您可以修改表单模板(user-form.component.html)以显示动态地址表格:
<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<div formArrayName="addresses">
<div *ngFor="let address of addresses.controls; let i = index" [formGroupName]="i" class="form-group">
<h3>Address {{i + 1}}</h3>
<label>Street:</label>
<input formControlName="street" class="form-control" placeholder="Street">
<div *ngIf="address.get('street').errors?.required" class="alert alert-danger">
Street is required.
</div>
<label>City:</label>
<input formControlName="city" class="form-control" placeholder="City">
<div *ngIf="address.get('city').errors?.required" class="alert alert-danger">
City is required.
</div>
<label>State:</label>
<input formControlName="state" class="form-control" placeholder="State">
<div *ngIf="address.get('state').errors?.required" class="alert alert-danger">
State is required.
</div>
<label>Zip:</label>
<input formControlName="zip" class="form-control" placeholder="Zip">
<div *ngIf="address.get('zip').errors?.required" class="alert alert-danger">
Zip is required.
</div>
<button (click)="removeAddress(i)">Remove Address</button>
</div>
<button (click)="addAddress()">Add Address</button>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
在上面的HTML片段中,*ngFor
指令用于循环循环遍布地址FormArray
中的FormGroup
实例。 formGroupName
指令用于将每个FormGroup
实例绑定到div
元素。单击“删除地址”按钮时,它会触发removeadDress方法,并在单击“添加地址”按钮时调用adddress方法。
请注意,对于地址FormGroup
中的每个字段,错误消息使用address.get('<field>').errors?.required
检查所需的验证错误。这里的地址变量是指地址中的每个FormGroup
实例。
结论
利用角反应形式的功能可以创建动态,直观和用户友好的Web应用程序。 Angular的反应性形式具有其灵活性和效率,是在角度应用中构造和处理形式的必不可少的工具。利用FormArray
和Nested FormGroup
之类的功能,您可以轻松地管理和验证复杂的动态形式。
享受编码!