我正在 Angular 中开发一个表单,我需要根据通过效果异步检索的条件动态构建 FormGroup。条件 (sameServices) 确定是否应在表单中包含附加字段 addressComplement 并且是必需的。
这是我的代码:
constructor(private shared: Shared) {
effect(() => {
this.business = this.shared.getBusiness();
});
if (this.business.sameServices) {
this.branchDataForm = new FormGroup({
address: new FormControl('', [Validators.required, Validators.minLength(3)]),
addressComplement: new FormControl('', [Validators.required, Validators.minLength(3)]),
});
} else {
this.branchDataForm = new FormGroup({
address: new FormControl('', [Validators.required, Validators.minLength(3)])
});
}
}
我的问题是:
构造函数在执行条件之前不会等待效果完成。
如果我将条件放在效果内,则 HTML 会失败,表明它无法找到控件,因为它们在模板渲染时尚未定义。
仅当必填字段存在并且基于条件(sameServices)满足其验证规则时,我才需要表单有效。
问题
基于异步条件,动态添加或删除控件及其验证器,在 Angular 中构建 FormGroup 的干净而有效的方法是什么? Angular 中是否有处理此类场景的最佳实践?
您可以尝试这种方法,首先我们在初始加载期间设置所有可能的控件,但我们确保所有这些控件都是
disabled
,以便表单不会验证它们。
branchDataForm = new FormGroup({
address: new FormControl({ value: '', disabled: true }, [
Validators.required,
Validators.minLength(3),
]),
addressComplement: new FormControl({ value: '', disabled: true }, [
Validators.required,
Validators.minLength(3),
]),
});
我们使用
[hidden]
属性,而不是在反应式表单中效果很好的 ngIf
,我们可以使用这种逻辑来隐藏隐藏字段。
<form [formGroup]="branchDataForm">
<input [hidden]="!showFields['address']" formControlName="address"/> <br/><br/>
<input [hidden]="!showFields['addressComplement']" formControlName="addressComplement"/><br/><br/>
</form>
信号完成后,我们只需使用以下函数启用控制即可。我们还设置隐藏/显示字段的布尔值。
enableFormFields(enableFields: Array<string>) {
this.branchDataForm.disable();
Object.keys(this.showFields).forEach((key: string) => {
this.showFields[key] = false;
});
enableFields.forEach((controlName: string) => {
const ctrl = this.branchDataForm.get(controlName);
ctrl?.enable();
this.showFields[controlName] = true;
});
}
constructor() {
effect(() => {
this.business = this.getBusiness();
if (this.business.sameServices) {
this.enableFormFields(['address', 'addressComplement']);
} else {
this.enableFormFields(['address']);
}
});
}
import { Component, effect } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import {
FormGroup,
ReactiveFormsModule,
FormControl,
Validators,
} from '@angular/forms';
import { of, delay } from 'rxjs';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<form [formGroup]="branchDataForm">
<input [hidden]="!showFields['address']" formControlName="address"/> <br/><br/>
<input [hidden]="!showFields['addressComplement']" formControlName="addressComplement"/><br/><br/>
</form>
`,
})
export class App {
getBusiness = toSignal(
of({
sameServices: false,
})
);
business: any;
branchDataForm = new FormGroup({
address: new FormControl({ value: '', disabled: true }, [
Validators.required,
Validators.minLength(3),
]),
addressComplement: new FormControl({ value: '', disabled: true }, [
Validators.required,
Validators.minLength(3),
]),
});
noAddress = true;
showFields: any = {};
enableFormFields(enableFields: Array<string>) {
this.branchDataForm.disable();
Object.keys(this.showFields).forEach((key: string) => {
this.showFields[key] = false;
});
enableFields.forEach((controlName: string) => {
const ctrl = this.branchDataForm.get(controlName);
ctrl?.enable();
this.showFields[controlName] = true;
});
}
constructor() {
effect(() => {
this.business = this.getBusiness();
if (this.business.sameServices) {
this.enableFormFields(['address', 'addressComplement']);
} else {
this.enableFormFields(['address']);
}
});
}
}
bootstrapApplication(App);