在 Angular 7 中处理大型响应式表单
Posted
技术标签:
【中文标题】在 Angular 7 中处理大型响应式表单【英文标题】:Handling big reactive forms in Angular 7 【发布时间】:2019-12-22 07:33:42 【问题描述】:我正在尝试寻找一种更好的方法来处理复杂的角度形式。 表格真的很大,我需要找到一种方法来降低复杂性。
这里是表单结构的一个例子:
"fieldA" : ...,
"fieldB" : ...,
"fieldC" : ...,
"profile":
"username": ...,
"email": ...,
"firstName": ...,
"lastName": ...,
...
,
"settings":
"enableEmailNotification": ...,
"notificationsEmail": ..., // required when enableEmailNotification
...
,
...
在某些情况下验证器会即时更改,例如当enableEmailNotification=true
时,组件会将Required
验证器添加到notificationsEmail
这里是研究过的选项:
选项 #0 - 经典
sample on github
这种方法使用一种形式和一种组件。
优点:
代码很多,但很简单缺点:
所有逻辑都在一个地方。对于我来说,这个组件变得太大并且难以阅读或维护 UI 也变得足够大了选项 #1 - 将 FormGroup 传递给子组件
sample on github
这种方法将formGroup
作为@Input()
属性发送到内部组件。
优点:
缩小部分视图缺点:
表单创建和验证规则仍在父组件中 仅缩小视图大小 在根组件中创建了验证逻辑,但在子组件中显示错误选项 #2 - 创建自定义 ControlValueAccessor
sample on github
基于this article,我们可以创建自定义的ControlValueAccessor,它将返回一个表单的一部分的对象。
优点:
以多种形式拆分表单。表单可以拆分为更小的独立部分。缺点:
为表单值保留 JS 对象。看起来不太好【问题讨论】:
【参考方案1】:我对大型复杂表单的策略是拥有一个包装组件和子组件。每个子组件都有自己的表单服务,包装器有一个主表单服务,其中注入了子表单服务,考虑一下
@Component(
selector: 'form-wrapper',
template: `
<form [formGroup]="form" (submit)="save()">
<sub-form-a></sub-form-a>
<sub-form-b></sub-form-b>
<input type="submit" value="Submit Form">
</form>
`,
providers: [MasterFormService, FormAService, FormBService]
)
export class FormWrapper
constructor(private formService: MasterFormService)
save()
// whatever save actions here
@Component( // form b compoent is basically the same
selector: 'sub-form-a',
template: `
... whatever template belongs to form a ..
`
)
export class FormAComponent
form: FormGroup
constructor(private formService: FormAService)
this.form = this.formService.form;
// form a specific actions
@Injectable()
export class MasterFormService
form: FormGroup;
constructor(private fb: FormBuilder, formAService: FormAService, formBService: FormBService)
this.form = this.fb.group(
groupA: this.formAService.form,
groupB: this.formBService.form,
);
@Injectable() // formB service is basically the same
export class FormAService
form: FormGroup;
constructor(private fb: FormBuilder)
this.form = this.fb.group(
.. whatever fields belong to form a ..
);
此方法创建高度可重用的子表单,并让您模块化/隔离表单逻辑和模板。我经常发现子表单通常属于多个地方,因此它使我的代码非常干燥。特别是在您的示例中,您可以轻松地在应用程序的其他地方重用设置表单和配置文件表单组件。一两次我什至再次嵌套了这个结构以获得极其复杂的形式。
缺点是结构可能看起来很复杂,但你很快就会习惯。
【讨论】:
谢谢。看起来很有趣。我会试一试的。 你将如何通知 sub-form-b 关于 sub-form-a 的变化,例如根据 sub-form-a 中的值添加或删除所需的验证器【参考方案2】:就个人而言,对于大型复杂表单,我希望将表单逻辑保留在一个组件中,这样很容易链接可观察对象,但使用几个服务作为助手。用于初始化表单和处理操作(返回新表单值、验证、授权等)
逻辑/跟踪在FormComponent
表单的初始化在FormInitService
动作在一个pr多个中处理FormActionService(s)
表单组件
export class FormComponent implements OnInit
constructor(
private formActionService: FormActionService,
private formInitService: FormInitService
)
ngOnInit()
this.form = this.FormInitService.getForm();
this._trackFieldA();
this._trackProfile();
// Track a single field
private _trackFieldA()
this.form.controls.fieldA.valueChanges.pipe(
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
).subscribe(fieldA =>
console.log('Field A Changed');
this.formActionService.doSomething();
);
// Track a group
// Use ['controls'] for nested controls to skip typechecking errors
private _trackProfile()
combineLatest(
this.form.controls.profile['controls'].username.valueChanges,
this.form.controls.profile['controls'].email.valueChanges,
).pipe(
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
).subscribe(profile =>
console.log('Some profile field changed');
this.formActionService.doSomething();
);
FormInitService
export class FormInitService
constructor(
private formBuilder: FormBuilder
)
public getForm(): FormGroup
return this.formBuilder.group(
fieldA: 'Some init value',
fieldB: 'Some init value',
profile: this.formBuilder.group(
username: 'Some init value',
email: 'Some init value',
...
),
...
);
FormActionService
export class FormActionService
public doSomething(): any | void
console.log('Something')
您在 FormComponent 和模板中仍然有相当多的代码,但它真的很容易阅读和维护。拆分成多个组件通常会变得非常混乱,尤其是在团队工作时,或者需要进行一些(巨大的)重构时。
【讨论】:
以上是关于在 Angular 7 中处理大型响应式表单的主要内容,如果未能解决你的问题,请参考以下文章
如何使用响应式表单将验证器添加到表单控件以从角度材料进行匹配输入