带有子组件和验证的 Angular 2 嵌套表单
Posted
技术标签:
【中文标题】带有子组件和验证的 Angular 2 嵌套表单【英文标题】:Angular 2 nested forms with child components and validation 【发布时间】:2017-11-08 13:02:47 【问题描述】:我正在尝试在 Angular 2 中实现一个带有验证的嵌套表单,我看过帖子并遵循了文档,但我真的很挣扎,希望你能指出我正确的方向。
我想要实现的是拥有一个包含多个子组件的经过验证的表单。这些子组件有点复杂,其中一些有更多的子组件,但是为了这个问题,我认为我们可以解决有父和子的问题。
我想要完成什么
有一个像这样工作的表单:
<div [formGroup]="userForm" novalidate>
<div>
<label>User Id</label>
<input formControlName="userId">
</div>
<div>
<label>Dummy</label>
<input formControlName="dummyInput">
</div>
</div>
这需要一个这样的类:
private userForm: FormGroup;
constructor(private fb: FormBuilder)
this.createForm();
private createForm(): void
this.userForm = this.fb.group(
userId: ["", Validators.required],
dummyInput: "", Validators.required]
);
这按预期工作,但现在我想解耦代码,并将“dummyInput”功能放在单独的不同组件中。这就是我迷路的地方。这是我尝试过的,我想我离答案不远了,但我真的没有想法,我对这个场景还很陌生:
parent.component.html
<div [formGroup]="userForm" novalidate>
<div>
<label>User Id</label>
<input formControlName="userId">
</div>
<div>
<dummy></dummy>
</div>
</div>
parent.component.ts
private createForm(): void
this.userForm = this.fb.group(
userId: ["", Validators.required],
dummy: this.fb.group(
dummyInput: ["", Validators.required]
)
);
children.component.html
<div [formGroup]="dummyGroup">
<label>Dummy Input: </label>
<input formControlName="dummyInput">
</div>
children.component.ts
private dummyGroup: FormGroup;
我知道代码有问题,但我确实遇到了障碍。任何帮助将不胜感激。
谢谢。
【问题讨论】:
Angular 2: Form containing child component的可能重复 【参考方案1】:FormGroupDirective
的替代方法(如@blacksheep's answer 中所述)是使用ControlContainer
,如下所示:
import FormGroup, ControlContainer from "@angular/forms";
export class ChildComponent implements OnInit
formGroup: FormGroup;
constructor(private controlContainer: ControlContainer)
ngOnInit()
this.formGroup = <FormGroup>this.controlContainer.control;
formGroup
可以设置在直接父级或更高级别(例如,父级的父级)。这使得可以在各种嵌套组件上传递 from 组,而无需 @Input()
s 链来传递 formGroup。在任何父级中设置formGroup
以使其通过子级中的ControlContainer
可用:
<... [formGroup]="myFormGroup">
【讨论】:
【参考方案2】:要获得对父表单的引用,只需使用它(可能在 Angular 2 中不可用。我已经用 Angular 6 测试过):
TS
import
FormGroup,
ControlContainer,
FormGroupDirective,
from "@angular/forms";
@Component(
selector: "app-leveltwo",
templateUrl: "./leveltwo.component.html",
styleUrls: ["./leveltwo.component.sass"],
viewProviders: [
provide: ControlContainer,
useExisting: FormGroupDirective
]
)
export class NestedLevelComponent implements OnInit
//form: FormGroup;
constructor(private parent: FormGroupDirective)
//this.form = form;
HTML
<input type="text" formControlName="test" />
【讨论】:
【参考方案3】:主要思想是你必须将formGroup和formControls视为变量,主要是javascript对象和数组。
所以我会输入一些代码来说明我的观点。下面的代码有点像你所拥有的。该表单是动态构建的,只是它被分成多个部分,每个部分包含其共享的字段和标签。
HTML 由 typescript 类支持。那些不在这里,因为它们没有什么特别之处。只有 FormSchemaUI、FormSectionUI 和 FormFieldUI 很重要。
将每段代码视为自己的文件。
另外请注意,formSchema: FormSchema 是我从服务接收的 JSON 对象。您没有看到定义的 UI 类的任何属性都是从它们的基本 Data 类继承的。这些不在此处介绍。 层次结构是: FormSchema 包含多个部分。一个部分包含多个字段。
<form (ngSubmit)="onSubmit()" #ciRegisterForm="ngForm" [formGroup]="formSchemaUI.MainFormGroup">
<button kendoButton (click)="onSubmit(ciRegisterForm)" [disabled]="!canSubmit()"> Register registerPageName </button>
<br /><br />
<app-ci-register-section *ngFor="let sectionUI of formSchemaUI.SectionsUI" [sectionUI]="sectionUI">
</app-ci-register-section>
<button kendoButton (click)="onSubmit(ciRegisterForm)" [disabled]="!canSubmit()"> Register registerPageName </button>
</form>
==============================================
<div class="row" [formGroup]="sectionUI.MainFormGroup">
<div class="col-md-12 col-lg-12" [formGroupName]="sectionUI.SectionDisplayId">
<fieldset class="section-border">
<legend class="section-border">sectionUI.Title</legend>
<ng-container *ngFor='let fieldUI of sectionUI.FieldsUI; let i=index; let even = even;'>
<div class="row" *ngIf="even">
<ng-container>
<div class="col-md-6 col-lg-6" app-ci-field-label-tuple [fieldUI]="fieldUI">
</div>
</ng-container>
<ng-container *ngIf="sectionUI.Fields[i+1]">
<div class="col-md-6 col-lg-6" app-ci-field-label-tuple [fieldUI]="sectionUI.FieldsUI[i+1]">
</div>
</ng-container>
</div>
</ng-container>
</fieldset>
</div>
</div>
==============================================
fieldUI.Label
==============================================
<ng-container>
<div class="row">
<div class="col-md-4 col-lg-4 text-right">
<label for="fieldUI.FieldDisplayId"> fieldUI.Label </label>
</div>
<div class="col-md-8 col-lg-8">
<div app-ci-field-edit [fieldUI]="fieldUI" ></div>
</div>
</div>
</ng-container>
==============================================
<ng-container [formGroup]="fieldUI.ParentSectionFormGroup">
<ng-container *ngIf="fieldUI.isEnabled">
<ng-container [ngSwitch]="fieldUI.ColumnType">
<input *ngSwitchCase="'HIDDEN'" type="hidden" id="fieldUI.FieldDisplayId" [value]="fieldUI.Value" />
<ci-field-textbox *ngSwitchDefault
[fieldUI]="fieldUI"
(valueChange)="onValueChange($event)"
class="fullWidth" style="width:100%">
</ci-field-textbox>
</ng-container>
</ng-container>
</ng-container>
==============================================
export class FormSchemaUI extends FormSchema
SectionsUI: Array<FormSectionUI>;
MainFormGroup: FormGroup;
static fromFormSchemaData(formSchema: FormSchema): FormSchemaUI
let formSchemaUI = new FormSchemaUI(formSchema);
formSchemaUI.SectionsUI = new Array<FormSectionUI>();
formSchemaUI.Sections.forEach(section =>
let formSectionUI = FormSectionUI.fromFormSectionData(section);
formSchemaUI.SectionsUI.push(formSectionUI);
);
formSchemaUI.MainFormGroup = FormSchemaUI.buildMainFormGroup(formSchemaUI);
return formSchemaUI;
static buildMainFormGroup(formSchemaUI: FormSchemaUI): FormGroup
let obj = ;
formSchemaUI.SectionsUI.forEach(sectionUI =>
obj[sectionUI.SectionDisplayId] = sectionUI.SectionFormGroup;
);
let sectionFormGroup = new FormGroup(obj);
return sectionFormGroup;
==============================================
export class FormSectionUI extends FormSection
constructor(formSection: FormSection)
this.SectionDisplayId = 'section' + this.SectionId.toString();
SectionDisplayId: string;
FieldsUI: Array<FormFieldUI>;
HiddenFieldsUI: Array<FormFieldUI>;
SectionFormGroup: FormGroup;
MainFormGroup: FormGroup;
ParentFormSchemaUI: FormSchemaUI;
static fromFormSectionData(formSection: FormSection): FormSectionUI
let formSectionUI = new FormSectionUI(formSection);
formSectionUI.FieldsUI = new Array<FormFieldUI>();
formSectionUI.HiddenFieldsUI = new Array<FormFieldUI>();
formSectionUI.Fields.forEach(field =>
let fieldUI = FormFieldUI.fromFormFieldData(field);
if (fieldUI.ColumnType != 'HIDDEN')
formSectionUI.FieldsUI.push(fieldUI);
else formSectionUI.HiddenFieldsUI.push(fieldUI);
);
formSectionUI.SectionFormGroup = FormSectionUI.buildFormSectionFormGroup(formSectionUI);
return formSectionUI;
static buildFormSectionFormGroup(formSectionUI: FormSectionUI): FormGroup
let obj = ;
formSectionUI.FieldsUI.forEach(fieldUI =>
obj[fieldUI.FieldDisplayId] = fieldUI.FieldFormControl;
);
let sectionFormGroup = new FormGroup(obj);
return sectionFormGroup;
==============================================
export class FormFieldUI extends FormField
constructor(formField: FormField)
super();
this.FieldDisplayId = 'field' + this.FieldId.toString();
this.ListItems = new Array<SelectListItem>();
public FieldDisplayId: string;
public FieldFormControl: FormControl;
public ParentSectionFormGroup: FormGroup;
public MainFormGroup: FormGroup;
public ParentFormSectionUI: FormSectionUI;
public ValueChange: EventEmitter<any> = new EventEmitter<any>();
static buildFormControl(formFieldUI:FormFieldUI): FormControl
let nullValidator = Validators.nullValidator;
let fieldKey: string = formFieldUI.FieldDisplayId;
let fieldValue: any;
switch (formFieldUI.ColumnType)
default:
fieldValue = formFieldUI.Value;
break;
let isDisabled = !formFieldUI.IsEnabled;
let validatorsArray: ValidatorFn[] = new Array<ValidatorFn>();
let asyncValidatorsArray: AsyncValidatorFn[] = new Array<AsyncValidatorFn>();
let formControl = new FormControl( value: fieldValue, disabled: isDisabled , validatorsArray, asyncValidatorsArray);
return formControl;
【讨论】:
【参考方案4】:import Directive from '@angular/core';
import ControlContainer, NgForm from '../../../node_modules/@angular/forms';
@Directive(
selector: '[ParentProvider]',
providers: [
provide: ControlContainer,
useFactory: function (form: NgForm)
return form;
,
deps: [NgForm]
`enter code here`
]
)
export class ParentProviderDirective
constructor()
<div ParentProvider >
for child
</div>
【讨论】:
【参考方案5】:您可以在您的子组件中添加一个 Input 以将 FormGroup 传递给它。并使用 FormGroupName 来传递您的 FormGroup 的名称:)
children.component.ts
@Input('group');
private dummyGroup: FormGroup;
parent.component.html
<div [formGroup]="userForm" novalidate>
<div>
<label>User Id</label>
<input formControlName="userId">
</div>
<div formGroupName="dummy">
<dummy [group]="userForm.controls['dummy']"></dummy>
</div>
</div>
【讨论】:
相关:***.com/questions/40172270/… 这部分:[group]="userForm.controls['dummy']" 是解决方案所在。我花了一天时间试图找到解决这个问题的方法,传递了一个组到子组件。我有一个 ngFor。 Tee 最终代码如下所示:[group]="formGroup.controls['section'+section.SectionId]" 嗨@dragos-durlut,如果你愿意解释它并分享一些代码,我很乐意接受答案。 @Mese 我已经添加并回答了代码支持。它应该是您问题的优雅解决方案。【参考方案6】:不会撒谎,不知道我怎么没早点找到这个帖子。
Angular 2: Form containing child component
解决方案是将子组件绑定到同一个formGroup,方法是将formGroup从父组件传递给子组件作为输入。
如果有人分享一段代码以其他方式解决问题,我很乐意接受。
【讨论】:
我已添加并由代码支持答案。它应该是您问题的优雅解决方案。 感谢您抽出宝贵的时间,我相信它会比我之前的回答更有帮助! 欢迎。如果您需要更多信息,请评论答案。 我还添加了更多代码。我忘记了section类。以上是关于带有子组件和验证的 Angular 2 嵌套表单的主要内容,如果未能解决你的问题,请参考以下文章