如何使用响应式表单将表单绑定到 Angular 6 中的模型?
How can I bind a form to a model in Angular 6 using reactive forms?
在 angular 6 之前,我使用 [(ngModel)]
将我的表单字段直接绑定到模型。现在已弃用(不能与反应形式一起使用)并且我不确定如何使用表单值更新我的模型。我可以使用 form.getRawValue()
但这需要我用新的 rawValue 替换我当前的模型 - 这不是有利的,因为我的主模型比本地表单模型更大并且具有更多的字段。
有什么想法吗?
正如在 Angular Documentation 中更全面地解释的那样,对于响应式表单,您不会将表单直接绑定到 您的 模型。相反,您使用 FormBuilder 构建一个 FormGroup 对象(本质上,"the form"),它将维护它自己的模型。
在构造过程中,您有机会在表单中设置初始值,这通常是在 您的 模型中进行的。
然后将模板中的表单控件绑定到表单模型。
用户与表单控件的交互会更新表单的模型。
当您准备好对表单数据进行操作时(例如 "submit" 表单),
您可以使用 FormGroup 的值 属性 或它的 getRawValue() 方法从表单字段中获取值 - 这两个行为不同,请参阅文档了解详细信息。
从表单中获取值后,如果您愿意,可以使用表单中的值更新您的模型。
不要使用 [(ngModel)]
!反应形式要好得多。他们使手动 ngModel
绑定过时,并且他们有一些非常好的内置功能,我将在这个答案中介绍其中的几个。
绑定到表单
如果您要绑定到表单控件(例如文本输入),请使用此模板语法:
<ng-container [formGroup]="this.myFormGroup">
<input type="text" formControlName="field1">
<input type="text" formControlName="field2">
<ng-container formGroupName="subgroupName">
<input type="text" formControlName="subfield2">
</ng-container>
<input type="text" formControlName="myRequiredField">
</ng-container>
(field1
、field2
、subgroupName
、subfield2
、myRequiredField
都是任意的控件和控件组名对应于您的表单部分,创建 FormGroup
对象时请参阅下文。)
FormGroup
模型的只读数据绑定在您的模板中的访问方式略有不同:
{{ this.myFormGroup.get('field1').value }}
{{ this.myFormGroup.get('subgroupName.subfield2').value }}
<!-- Hint: use an array if you have field names that contain "." -->
{{ this.myFormGroup.get(['subgroupName', 'subfield2']).value }}
正在创建 FormGroup
在您的组件 class 中,在 constructor()
中(这应该在模板呈现之前),使用以下语法构建一个表单组来与此表单对话:
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
...
public readonly myFormGroup: FormGroup;
...
constructor(private readonly formBuilder: FormBuilder) {
this.myFormGroup = this.formBuilder.group({
field1: [],
field2: [],
subgroupName: this.formBuilder.group({
subfield2: [],
}),
myRequiredField: ['', Validators.required],
});
this.retrieveData();
}
正在用数据填写您的表单
如果您的组件需要在加载时从服务中检索数据,您必须确保它在构建表单后开始传输,然后使用 patchValue()
将对象中的数据放入FormGroup
:
private retrieveData(): void {
this.dataService.getData()
.subscribe((res: SomeDataStructure) => {
// Assuming res has a structure like:
// res = {
// field1: "some-string",
// field2: "other-string",
// subgroupName: {
// subfield2: "another-string"
// },
// }
// Values in res that don't line up to the form structure
// are discarded. You can also pass in your own object you
// construct ad-hoc.
this.myFormGroup.patchValue(res);
});
}
从表单中获取数据
现在,假设您的用户点击了提交,现在您需要从您的表单中取回数据,然后 POST
通过服务将其返回给您的 API。只需使用 getRawValue
:
public onClickSubmit(): void {
if (this.myFormGroup.invalid) {
// stop here if it's invalid
alert('Invalid input');
return;
}
this.myDataService.submitUpdate(this.myFormGroup.getRawValue())
.subscribe((): void => {
alert('Saved!');
});
}
所有这些技术消除了对任何 [(ngModel)]
绑定的需要,因为表单在 FormGroup
对象中维护了自己的内部模型。
您可以订阅表单组中的更改并使用它来更新您的模型。但这并不安全。因为您必须确保您的表单字段与模型字段匹配或添加模型中字段存在的验证。
bindModelToForm(model: any, form: FormGroup) {
const keys = Object.keys(form.controls);
keys.forEach(key => {
form.controls[key].valueChanges.subscribe(
(newValue) => {
model[key] = newValue;
}
)
});
}
我的服务的完整代码:
referenceFields - 意味着如果你有像 student: { name, group }
这样的复杂字段,其中 group 是一个引用模型,你只需要能够获得此模型的 ID:
import { Injectable } from '@angular/core';
import { FormGroup } from "@angular/forms";
@Injectable({
providedIn: 'root'
})
export class FormService {
constructor() {
}
bindModelToForm(model: any, form: FormGroup, referenceFields: string[] = []) {
if (!this.checkFieldsMatching(model, form)) {
throw new Error('FormService -> bindModelToForm: Model and Form fields is not matching');
}
this.initForm(model, form);
const formKeys = Object.keys(form.controls);
formKeys.forEach(key => {
if (referenceFields.includes(key)) {
form.controls[key].valueChanges.subscribe(
(newValue) => {
model[key] = newValue.id;
}
)
} else {
form.controls[key].valueChanges.subscribe(
(newValue) => {
model[key] = newValue;
}
)
}
});
}
private initForm(model: any, form: FormGroup) {
const keys = Object.keys(form.controls);
keys.forEach(key => {
form.controls[key].setValue(model[key]);
});
}
private checkFieldsMatching(model: any, form: FormGroup): boolean {
const formKeys = Object.keys(form.controls);
const modelKeys = Object.keys(model);
formKeys.forEach(formKey => {
if (!modelKeys.includes(formKey)) {
return false;
}
});
return true;
}
}
在 angular 6 之前,我使用 [(ngModel)]
将我的表单字段直接绑定到模型。现在已弃用(不能与反应形式一起使用)并且我不确定如何使用表单值更新我的模型。我可以使用 form.getRawValue()
但这需要我用新的 rawValue 替换我当前的模型 - 这不是有利的,因为我的主模型比本地表单模型更大并且具有更多的字段。
有什么想法吗?
正如在 Angular Documentation 中更全面地解释的那样,对于响应式表单,您不会将表单直接绑定到 您的 模型。相反,您使用 FormBuilder 构建一个 FormGroup 对象(本质上,"the form"),它将维护它自己的模型。 在构造过程中,您有机会在表单中设置初始值,这通常是在 您的 模型中进行的。
然后将模板中的表单控件绑定到表单模型。 用户与表单控件的交互会更新表单的模型。
当您准备好对表单数据进行操作时(例如 "submit" 表单), 您可以使用 FormGroup 的值 属性 或它的 getRawValue() 方法从表单字段中获取值 - 这两个行为不同,请参阅文档了解详细信息。
从表单中获取值后,如果您愿意,可以使用表单中的值更新您的模型。
不要使用 [(ngModel)]
!反应形式要好得多。他们使手动 ngModel
绑定过时,并且他们有一些非常好的内置功能,我将在这个答案中介绍其中的几个。
绑定到表单
如果您要绑定到表单控件(例如文本输入),请使用此模板语法:
<ng-container [formGroup]="this.myFormGroup">
<input type="text" formControlName="field1">
<input type="text" formControlName="field2">
<ng-container formGroupName="subgroupName">
<input type="text" formControlName="subfield2">
</ng-container>
<input type="text" formControlName="myRequiredField">
</ng-container>
(field1
、field2
、subgroupName
、subfield2
、myRequiredField
都是任意的控件和控件组名对应于您的表单部分,创建 FormGroup
对象时请参阅下文。)
FormGroup
模型的只读数据绑定在您的模板中的访问方式略有不同:
{{ this.myFormGroup.get('field1').value }}
{{ this.myFormGroup.get('subgroupName.subfield2').value }}
<!-- Hint: use an array if you have field names that contain "." -->
{{ this.myFormGroup.get(['subgroupName', 'subfield2']).value }}
正在创建 FormGroup
在您的组件 class 中,在 constructor()
中(这应该在模板呈现之前),使用以下语法构建一个表单组来与此表单对话:
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
...
public readonly myFormGroup: FormGroup;
...
constructor(private readonly formBuilder: FormBuilder) {
this.myFormGroup = this.formBuilder.group({
field1: [],
field2: [],
subgroupName: this.formBuilder.group({
subfield2: [],
}),
myRequiredField: ['', Validators.required],
});
this.retrieveData();
}
正在用数据填写您的表单
如果您的组件需要在加载时从服务中检索数据,您必须确保它在构建表单后开始传输,然后使用 patchValue()
将对象中的数据放入FormGroup
:
private retrieveData(): void {
this.dataService.getData()
.subscribe((res: SomeDataStructure) => {
// Assuming res has a structure like:
// res = {
// field1: "some-string",
// field2: "other-string",
// subgroupName: {
// subfield2: "another-string"
// },
// }
// Values in res that don't line up to the form structure
// are discarded. You can also pass in your own object you
// construct ad-hoc.
this.myFormGroup.patchValue(res);
});
}
从表单中获取数据
现在,假设您的用户点击了提交,现在您需要从您的表单中取回数据,然后 POST
通过服务将其返回给您的 API。只需使用 getRawValue
:
public onClickSubmit(): void {
if (this.myFormGroup.invalid) {
// stop here if it's invalid
alert('Invalid input');
return;
}
this.myDataService.submitUpdate(this.myFormGroup.getRawValue())
.subscribe((): void => {
alert('Saved!');
});
}
所有这些技术消除了对任何 [(ngModel)]
绑定的需要,因为表单在 FormGroup
对象中维护了自己的内部模型。
您可以订阅表单组中的更改并使用它来更新您的模型。但这并不安全。因为您必须确保您的表单字段与模型字段匹配或添加模型中字段存在的验证。
bindModelToForm(model: any, form: FormGroup) {
const keys = Object.keys(form.controls);
keys.forEach(key => {
form.controls[key].valueChanges.subscribe(
(newValue) => {
model[key] = newValue;
}
)
});
}
我的服务的完整代码:
referenceFields - 意味着如果你有像 student: { name, group }
这样的复杂字段,其中 group 是一个引用模型,你只需要能够获得此模型的 ID:
import { Injectable } from '@angular/core';
import { FormGroup } from "@angular/forms";
@Injectable({
providedIn: 'root'
})
export class FormService {
constructor() {
}
bindModelToForm(model: any, form: FormGroup, referenceFields: string[] = []) {
if (!this.checkFieldsMatching(model, form)) {
throw new Error('FormService -> bindModelToForm: Model and Form fields is not matching');
}
this.initForm(model, form);
const formKeys = Object.keys(form.controls);
formKeys.forEach(key => {
if (referenceFields.includes(key)) {
form.controls[key].valueChanges.subscribe(
(newValue) => {
model[key] = newValue.id;
}
)
} else {
form.controls[key].valueChanges.subscribe(
(newValue) => {
model[key] = newValue;
}
)
}
});
}
private initForm(model: any, form: FormGroup) {
const keys = Object.keys(form.controls);
keys.forEach(key => {
form.controls[key].setValue(model[key]);
});
}
private checkFieldsMatching(model: any, form: FormGroup): boolean {
const formKeys = Object.keys(form.controls);
const modelKeys = Object.keys(model);
formKeys.forEach(formKey => {
if (!modelKeys.includes(formKey)) {
return false;
}
});
return true;
}
}