问题
I have the following classes:
class License {
name:string;
.. lots of other fields.
nameAttributes:NameAttributes[];
}
class nameAttributes:NameAttributes{
index:number;
attribute:string;
}
I know I can created the form like this, but it requires that I manually create each field(control) and every time the licences class changes, I have to update the class and this formGroup with the new fields.
this.licenseForm = formBuilder.group({
name:['name value'],
otherFields:['their values'],
nameAttributes: this.formBuilder.array([
this.formBuilder.group({
index:[1],
attribute:["attr1"]
}),
this.formBuilder.group({
index:[2],
attribute:["attr2"]
})
])
});
I would prefer if I could just pass the license class into formBuilder and it would create the necessary FormGroups automatically and make them available based on their name, so the following would create two groups, a "license" group and a nested "nameAttributes" group with all of the associated controls for license and nameAttributes.
this.licenseForm = formBuilder.group({
license:formBuilder.group(license)
});
Am I missing something or is this just not possible without some serious class introspection code?
回答1:
If your object has data, of course you can do it
Take a look to this stackblitz
You has a function like
createForm(data:any):FormGroup
{
const group=new FormGroup({});
for (let key in data)
{
if (Array.isArray(data[key])) //if is an array
{ //first create the formArray
group.addControl(key,new FormArray([this.createForm(data[key][0])]))
for (let i=1;i<data[key].length;i++) //after add the rest of the elements
(group.get(key) as FormArray).push(this.createForm(data[key][i]))
}
else
{
if (typeof(data[key])=="object") //if is an object we add a formGroup
group.addControl(key,this.createForm(data[key]))
else //add a simple control
group.addControl(key,new FormControl(data[key]))
}
}
return group
}
And call it as
this.form=this.createForm(this.obj)
回答2:
@cyberthreat as promised, here's a version with ngx-sub-form.
First of all, here's the link of a live demo: https://stackblitz.com/edit/angular-td2iew
Now let's see how it's built:
app.component.ts
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public licenseUpdated(license: License): void {
console.log(JSON.stringify(license, null, 2));
}
}
app.component.html
<app-license-form (licenseUpdated)="licenseUpdated($event)"></app-license-form>
Nothing too fancy here but notice the fact that from this component we absolutely don't care how we get the data of the license. We just want to be warned as soon as there's an update.
license-form.component.ts
@Component({
selector: 'app-license-form',
templateUrl: './license-form.component.html',
styleUrls: ['./license-form.component.css']
})
export class LicenseFormComponent extends NgxRootFormComponent<License> {
@DataInput()
@Input('license')
public dataInput: License | null | undefined;
@Output('licenseUpdated') public dataOutput: EventEmitter<License> = new EventEmitter();
protected getFormControls(): Controls<License> {
return {
name: new FormControl(null, [Validators.required]),
nameAttributes: new FormControl(null, [Validators.required]),
};
}
}
license-form.component.html
<form [formGroup]="formGroup">
<div>
Name<br>
<input type="text" [formControlName]="formControlNames.name">
</div>
<div>
License attributes<br>
<app-license-attributes-form [formControlName]="formControlNames.nameAttributes"></app-license-attributes-form>
</div>
<button class="save" (click)="manualSave()">Save form</button>
(look at your console to see when the form is saved!)
</form>
<div class="values">
Form group values
<pre>{{ formGroupValues | json }}</pre>
</div>
Here, if you're not familiar with ngx-sub-form I'd invite you to read that blog post: https://dev.to/maxime1992/building-scalable-robust-and-type-safe-forms-with-angular-3nf9 and/or the README on the project: https://github.com/cloudnc/ngx-sub-form
The important thing to notice here is that we only care about primitive values and everything else is broken down into sub form components! Also, everything there is type safe (ts and html!) regarding the form.
license-attributes-form.component.ts
interface LicenseAttributesForm {
attributes: NameAttribute[];
}
@Component({
selector: 'app-license-attributes-form',
templateUrl: './license-attributes-form.component.html',
styleUrls: ['./license-attributes-form.component.css'],
providers: subformComponentProviders(LicenseAttributesFormComponent)
})
export class LicenseAttributesFormComponent extends NgxSubFormRemapComponent<NameAttribute[], LicenseAttributesForm> {
protected getFormControls(): Controls<LicenseAttributesForm> {
return {
attributes: new FormArray([], [Validators.required]),
};
}
protected transformToFormGroup(obj: NameAttribute[]): LicenseAttributesForm {
return {
attributes: obj ? obj : [],
};
}
protected transformFromFormGroup(formValue: LicenseAttributesForm): NameAttribute[] | null {
return formValue.attributes;
}
public addAttribute(): void {
(this.formGroupControls.attributes as FormArray).push(
this.createFormArrayControl(
'attributes',
{
index: null,
attribute: null
}
)
);
}
public removeAttribute(index: number): void {
(this.formGroupControls.attributes as FormArray).removeAt(index);
}
public createFormArrayControl(
key: any,
value: any,
): FormControl {
return new FormControl(value, [Validators.required]);
}
}
license-attributes-form.component.html
<div [formGroup]="formGroup">
<button (click)="addAttribute()">Add an attribute</button>
<div
class="attribute"
formArrayName="attributes"
*ngFor="let attribute of formGroupControls.attributes.controls; let i = index"
>
<app-license-attribute-form [formControl]="attribute"></app-license-attribute-form>
<button (click)="removeAttribute(i)">Delete</button>
</div>
</div>
And finally the last one
license-attribute-form.component.ts
@Component({
selector: 'app-license-attribute-form',
templateUrl: './license-attribute-form.component.html',
styleUrls: ['./license-attribute-form.component.css'],
providers: subformComponentProviders(LicenseAttributeFormComponent)
})
export class LicenseAttributeFormComponent extends NgxSubFormComponent<NameAttribute> {
protected getFormControls(): Controls<NameAttribute> {
return {
index: new FormControl(null, [Validators.required]),
attribute: new FormControl(null, [Validators.required]),
};
}
}
license-attribute-form.component.html
<form [formGroup]="formGroup">
<div>
Index<br>
<input type="number" [formControlName]="formControlNames.index">
</div>
<div>
Attribute<br>
<input type="text" [formControlName]="formControlNames.attribute">
</div>
</form>
I'd really encourage you to have a look on Stackblitz and play around with that demo it'll be the easier way to understand and discover I think :)
来源:https://stackoverflow.com/questions/56671796/designing-nested-reactive-forms-from-nested-classes