|
1.2 フォーム モデルの確立
レスポンシブ フォームとテンプレート駆動型フォームの両方で、ユーザーが操作するフォーム入力要素とコンポーネント モデル内のフォーム データの間の値の変更を追跡します。 2 つのメソッドは、基礎となる ビルディング ブロック
の同じセットを共有しており、一般的に使用される コントロール インスタンスの形成方法
と を管理する方法が異なるだけです。
1.3 一般的に使用されるフォームの基本クラス
レスポンシブ フォームとテンプレート駆動型フォームはどちらも、次の基本クラスに基づいて構築されています。
FormControl インスタンスは、個々のフォーム コントロールの値と検証ステータスを追跡するために使用されます。
FormGroup は、フォーム コントロール グループの値とステータスを追跡するために使用されます。 - FormArray は、フォーム コントロール配列の値と状態を追跡するために使用されます。
- ControlValueAccessor は、Angular の FormControl インスタンスとネイティブ DOM 要素の間のブリッジを作成するために使用されます。
-
- 2. レスポンシブ フォーム
レスポンシブ フォームは、明示的かつ不変の方法を使用して、特定の時点でのフォームの状態を管理します。フォームの状態が変更されるたびに新しい状態が返されるため、変更されたモデルの整合性が維持されます。リアクティブ フォームは Observable
ストリームを中心に構築されており、フォームの入力と値は、これらの入力値で構成されるストリームを通じて提供され、同期的にアクセスできます。
2.1 基本的なフォーム コントロールを追加する
フォーム コントロールを使用するには 3 つの手順があります。
リアクティブ フォーム モジュールをアプリケーションに登録します。このモジュールは、リアクティブ フォームで使用するいくつかのディレクティブを宣言します。
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
// other imports ...
ReactiveFormsModule
],
})
export class AppModule { }
フォーム コントロールを登録するには、FormControl クラスをインポートし、FormControl の新しいインスタンスを作成し、それをクラスのプロパティとして保存します。
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-name-editor',
templateUrl: './name-editor.component.html',
styleUrls: ['./name-editor.component.css']
})
export class NameEditorComponent {
name = new FormControl('');
}
FormControl のコンストラクター を使用して、初期値
を設定できます。この例では、
空の文字列 です。これらのコントロールをコンポーネント クラスに作成すると、フォーム コントロールの状態を 監視
、変更
、および 検証
することができます。
コンポーネント クラスでコントロールを作成した後、それをテンプレートのフォーム コントロールに関連付ける必要があります。テンプレートを変更し、formControl バインディングをフォーム コントロールに追加します。formControl は、ReactiveFormsModule の FormControlDirective によって提供されます。
<label>
Name:
<input type="text" [formControl]="name">
</label>
2.2 フォーム コントロールの値の表示
次の方法で値を表示できます。
By監視可能なオブジェクト valueChanges
を使用すると、テンプレートで AsyncPipe- を使用するか、コンポーネント クラスで
subscribe()
メソッドを使用して、フォーム値の変更をリッスンできます。
value 属性を使用します。現在の値のスナップショットを取得できます。
<label>
Name:
<input type="text" [formControl]="name">
</label>
Value: {{ name.value }}
public name = new FormControl('test');
public testValueChange() {
this.name.valueChanges.subscribe({
next: value => {
console.log("name value is: " + value);
}
})
}
- 2.3 フォーム コントロールの値を置き換える
レスポンシブ フォームには、プログラム的な方法で コントロールを変更するメソッドもいくつかあります。 value を使用すると、ユーザーの操作を必要とせずにコントロールの値を柔軟に変更できます。 FormControl は、このフォーム コントロールの値を変更し、コントロール構造に対応する値の構造を検証する
setValue() メソッドを提供します。たとえば、バックエンド API またはサービスからフォーム データを受信すると、setValue() メソッドを通じて元の値を新しい値に置き換えることができます。 <pre class="brush:js;toolbar:false;">updateName() {
this.name.setValue(&#39;Nancy&#39; + new Date().getTime());
}</pre><pre class="brush:js;toolbar:false;"><p>
<button (click)="updateName()">Update Name</button>
</p></pre>
2.4 グループ フォーム コントロール
フォームには通常、複数の 相互に関連するコントロールが含まれます。リアクティブ フォームでは、複数の関連コントロールを同じ入力フォームにグループ化する 2 つの方法が提供されます。
フォーム グループ
は、一緒に管理できる一連のコントロールを含むフォームを定義します。このセクションでは、フォーム グループの基本について説明します。 フォーム グループをネストする- により、より複雑なフォームを作成することもできます。
フォーム配列
実行時にコントロールを追加および削除できる動的フォームを定義します。 フォーム配列をネストすることで、より複雑なフォームを作成することもできます。-
このコンポーネントにフォーム グループを追加するには、次の手順に従います。
FormGroupimport { FormControl, FormGroup } from '@angular/forms';
profileForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
});
// 可以整个获取值
public onSubmit() {
// TODO: Use EventEmitter with form value
console.warn(this.profileForm.value);// {firstName: "", lastName: ""}
}
// 可以借助 valueChanges 整个可观察对象整个获取值
this.profileForm.valueChanges.subscribe( {
next: value => {
console.log("name value is: " + JSON.stringify(value)); // dashboard.component.ts:53 name value is: {"firstName":"dddd","lastName":"bb"}
}
})
// 可以通过后期单个控件单独获取值
this.profileForm.get('firstName').valueChanges.subscribe({
next: value => {
console.log("First Name is: " + value); // First Name is: aa
}
ps: 这个 FormGroup 用对象的形式提供了它的模型值,这个值来自组中每个控件的值
。 FormGroup 实例拥有和 FormControl 实例相同的属性
(比如 value、untouched)和方法(比如 setValue())。
这个表单组还能跟踪其中每个控件的状态及其变化,所以如果其中的某个控件的状态或值变化了,父控件也会发出一次新的状态变更或值变更事件。该控件组的模型来自它的所有成员。在定义了这个模型之后,你必须更新模板,来把该模型反映到视图中。
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<label>
First Name:
<input type="text" formControlName="firstName">
</label>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
<button type="submit" [disabled]="!profileForm.valid">Submit</button>
</form>
2.5 创建嵌套的表单组
表单组可以同时接受单个表单控件实例和其它表单组实例作为其子控件。这可以让复杂的表单模型更容易维护,并在逻辑上把它们分组到一起。
要制作更复杂的表单,请遵循如下步骤。
要在 profileForm 中创建一个嵌套组,就要把一个嵌套的 address 元素添加到此表单组的实例中。
public profileForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
address: new FormGroup({
street: new FormControl(''),
city: new FormControl(''),
state: new FormControl(''),
zip: new FormControl('')
})
});
// 可以借助 valueChanges 整个可观察对象整个获取值
this.profileForm.valueChanges.subscribe( {
next: value => {
console.log("name value is: " + JSON.stringify(value));// name value is: {"firstName":"","lastName":"","address":{"street":"b","city":"","state":"","zip":""}}
}
});
// 可以通过后期单个控件单独获取值
this.profileForm.get('firstName').valueChanges.subscribe({
next: value => {
console.log("First Name is: " + value);
}
});
// 可以获取form组件某个form组的整个值
this.profileForm.get('address').valueChanges.subscribe(({
next: value => {
console.log('address value is: ' + JSON.stringify(value));// address value is: {"street":"b","city":"","state":"","zip":""}
}
}));
// 可以获取form组件某个form组的某个formcontrol实例的值
this.profileForm.get('address').get('street').valueChanges.subscribe(({
next: value => {
console.log('street value is: ' + value);// street value is: b
}
}));
在修改了组件类中的模型之后,还要修改模板,来把这个 FormGroup 实例对接到它的输入元素。
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<label>
First Name:
<input type="text" formControlName="firstName">
</label>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
<div formGroupName="address">
<h3>Address</h3>
<label>
Street:
<input type="text" formControlName="street">
</label>
<label>
City:
<input type="text" formControlName="city">
</label>
<label>
State:
<input type="text" formControlName="state">
</label>
<label>
Zip Code:
<input type="text" formControlName="zip">
</label>
</div>
<button type="submit" [disabled]="!profileForm.valid">Submit</button>
</form>
2.6 更新部分数据模型
当修改包含多个 FormGroup 实例的值时,你可能只希望更新模型中的一部分,而不是完全替换掉。
有两种更新模型值的方式:
- 使用
setValue()
方法来为单个控件
设置新值。 setValue() 方法会严格遵循表单组的结构
,并整体性替换控件的值
。
- 使用
patchValue()
方法可以用对象中所定义的任何属性
为表单模型进行替换。
setValue() 方法的严格检查可以帮助你捕获复杂表单嵌套中的错误,而 patchValue() 在遇到那些错误时可能会默默的失败。
public updateProfile() {
// profileForm 模型中只有 firstName 和 street 被修改了。注意,street 是在 address 属性的对象中被修改的。这种结构是必须的,因为 patchValue() 方法要针对模型的结构进行更新。patchValue() 只会更新表单模型中所定义的那些属性。
this.profileForm.patchValue({
firstName: 'Nancy' + new Date().getTime(),
address: {
street: '123 Drew Street' + new Date().getTime()
}
});
// ERROR Error: Must supply a value for form control with name: 'lastName'.
// setValue() 方法会严格遵循表单组的结构
this.profileForm.setValue({
firstName: 'Nancy' + new Date().getTime(),
address: {
street: '123 Drew Street' + new Date().getTime()
}
});
}
2.7 创建动态表单
FormArray 是 FormGroup 之外的另一个选择,用于管理任意数量的匿名控件。像 FormGroup 实例一样,你也可以往 FormArray 中动态插入和移除控件,并且 FormArray 实例的值和验证状态也是根据它的子控件计算得来的。 不过,你不需要为每个控件定义一个名字作为 key,因此,如果你事先不知道子控件的数量,这就是一个很好的选择。
要定义一个动态表单,请执行以下步骤。
通过把一组(从零项到多项)控件定义在一个数组中来初始化一个 FormArray。为 profileForm 添加一个 aliases 属性,把它定义为 FormArray 类型。
import { FormControl, FormGroup, FormArray } from '@angular/forms';
public profileForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
address: new FormGroup({
street: new FormControl(''),
city: new FormControl(''),
state: new FormControl(''),
zip: new FormControl('')
}),
aliases: new FormArray([
new FormControl('1')
])
});
public aliases = (<FormArray>this.profileForm.get('aliases'));
public addAlias() {
(<FormArray>this.profileForm.get('aliases')).push(new FormControl('1'));
}
// 获取整个 formArray 的数据
this.profileForm.get('aliases').valueChanges.subscribe({
next: value => {
console.log('aliases values is: ' + JSON.stringify(value)); // aliases values is: ["1","3"]
}
});
// 获取 formArray 中单个 formControl 的数据
(<FormArray>this.profileForm.get('aliases')).controls[0].valueChanges.subscribe({
next: value => {
console.log('aliases[0] values is: ' + value); // aliases[0] values is: 0
}
})
要想为表单模型添加 aliases,你必须把它加入到模板中供用户输入。和 FormGroupNameDirective 提供的 formGroupName 一样,FormArrayNameDirective 也使用 formArrayName 在这个 FormArray 实例和模板之间建立绑定
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
<label>
First Name:
<input type="text" formControlName="firstName">
</label>
<label>
Last Name:
<input type="text" formControlName="lastName">
</label>
<div formGroupName="address">
<h3>Address</h3>
<label>
Street:
<input type="text" formControlName="street">
</label>
<label>
City:
<input type="text" formControlName="city">
</label>
<label>
State:
<input type="text" formControlName="state">
</label>
<label>
Zip Code:
<input type="text" formControlName="zip">
</label>
</div>
<div formArrayName="aliases">
<h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>
<div *ngFor="let alias of aliases.controls; let i=index">
<!-- The repeated alias template -->
<label>
Alias:
<input type="text" [formControlName]="i">
</label>
</div>
</div>
</form>
2.8 响应式表单 API 汇总
类 |
说明 |
AbstractControl |
所有三种表单控件类(FormControl、FormGroup 和 FormArray)的抽象基类。它提供了一些公共的行为和属性。 |
FormControl |
管理单体表单控件的值和有效性状态。它对应于 HTML 的表单控件,比如 或 。 |
FormGroup |
管理一组 AbstractControl 实例的值和有效性状态。该组的属性中包括了它的子控件。组件中的顶层表单就是 FormGroup。 |
FormArray |
管理一些 AbstractControl 实例数组的值和有效性状态。 |
FormBuilder |
一个可注入的服务,提供一些用于提供创建控件实例的工厂方法。 |
三、模板驱动表单
在模板驱动表单中,表单模型是隐式的,而不是显式的。指令 NgModel 为指定的表单元素创建并管理一个 FormControl 实例。
下面的组件使用模板驱动表单为单个控件实现了同样的输入字段。
import { Component } from '@angular/core';
@Component({
selector: 'app-template-favorite-color',
template: `
Favorite Color: <input type="text" [(ngModel)]="favoriteColor">
`
})
export class FavoriteColorComponent {
favoriteColor = '';
}
四、响应式表单验证表单输入
在组件类中直接
把验证器函数添加到表单控件模型
上(FormControl)。然后,一旦控件发生了变化,Angular 就会调用这些函数。
4.1 验证器(Validator)函数
验证器函数可以是同步函数,也可以是异步函数。
- 同步验证器:这些同步函数接受一个控件实例,然后返回
一组验证错误或 null
。你可以在实例化一个 FormControl 时把它作为构造函数的第二个参数
传进去。
- 异步验证器 :这些异步函数接受一个控件实例并返回
一个 Promise 或 Observable
,它稍后
会发出一组验证错误或 null
。在实例化 FormControl 时,可以把它们作为第三个参数
传入。
出于性能方面的考虑,只有在所有同步验证器都通过之后,Angular 才会运行异步验证器。当每一个异步验证器都执行完之后,才会设置这些验证错误。
4.2 内置验证器函数
在模板驱动表单中用作属性的那些内置验证器,比如 required 和 minlength,也都可以作为 Validators 类中的函数使用
public profileForm = new FormGroup({
firstName: new FormControl('', [
Validators.required
]),
});
this.profileForm.get('firstName').valueChanges.subscribe({
next: value => {
console.log("First Name is: " + value);
console.log(this.profileForm.get('firstName').errors);// { required: true } | null
}
});
<form [formGroup]="profileForm">
<label>
First Name:
<input type="text" formControlName="firstName">
<div *ngIf="firstName.errors?.required">
Name is required.
</div>
</label>
</form>
4.3 定义自定义验证器
内置的验证器并不是总能精确匹配应用中的用例,因此有时你需要创建一个自定义验证器。
public profileForm = new FormGroup({
firstName: new FormControl('', [
Validators.required,
this.forbiddenNameValidator(/bob/i)
])
});
public forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): {[key: string]: any} | null => {
const forbidden = nameRe.test(control.value);
return forbidden ? {forbiddenName: {value: control.value}} : null;
};
}
get firstName() { return this.profileForm.get('firstName'); }
this.profileForm.get('firstName').valueChanges.subscribe({
next: value => {
console.log("First Name is: " + value); // First Name is: bob
console.log(JSON.stringify(this.profileForm.get('firstName').errors));// {"forbiddenName":{"value":"bob"}} | null
}
});
4.4 跨字段交叉验证
跨字段交叉验证器是一种自定义验证器
,可以对表单中不同字段的值进行比较,并针对它们的组合进行接受或拒绝。
下列交叉验证的例子说明了如何进行如下操作:
- 根据两个兄弟控件的值验证响应式表单或模板驱动表单的输入,
- 当用户与表单交互过,且验证失败后,就会显示描述性的错误信息
要想在单个自定义验证器中计算这两个控件,你就必须在它们共同的祖先控件中执行验证: FormGroup。你可以在 FormGroup 中查询它的子控件,从而让你能比较它们的值。要想给 FormGroup 添加验证器,就要在创建时把一个新的验证器传给它的第二个参数。
this.profileForm.valueChanges.subscribe( {
next: value => {
console.log(JSON.stringify(this.profileForm.errors));// {"identityRevealed":true} | null
}
});
public profileForm = new FormGroup({
firstName: new FormControl('', [
Validators.required,
]),
lastName: new FormControl(''),
}, { validators: this.identityRevealedValidator});
public identityRevealedValidator(control: FormGroup): ValidationErrors | null{
const firstName = control.get('firstName');
const lastName = control.get('lastName');
return firstName && lastName && firstName.value === lastName.value ? { identityRevealed: true } : null;
};
4.5 创建异步验证器
异步验证器实现了 AsyncValidatorFn
和 AsyncValidator
接口。它们与其同步版本非常相似,但有以下不同之处。
- validate() 函数必须返回一个
Promise 或可观察对象
,
- 返回的可观察对象必须是
有尽
的,这意味着它必须在某个时刻完成(complete)
。要把无尽的可观察对象转换成有尽的,可以在管道中加入过滤操作符,比如 first、last、take 或 takeUntil。
异步验证在同步验证完成后才会发生
,并且只有在同步验证成功时才会执行。如果更基本的验证方法已经发现了无效输入,那么这种检查顺序就可以让表单避免使用昂贵的异步验证流程(例如 HTTP 请求)。
4.6 触发某个formControlName
let formControl = this.profileForm.get('firstName');
formControl.updateValueAndValidity();
更多编程相关知识,请访问:编程视频!!