I am figuring out how to refactor a complex form into smaller forms.
Essentially there is a parent form that will have a an array of child components which contain their owner forms.
Parent TS
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
form: FormGroup = this.fb.group({
mainAddress: [null, Validators.required],
addresses: this.fb.array([]),
});
constructor(private fb: FormBuilder) {}
get addresses() {
return this.form.controls['addresses'] as FormArray;
}
removeAddress(i: number) {
this.addresses.removeAt(i);
}
addAddress() {
this.addresses.push(
this.fb.control({
address: [null, Validators.required],
})
);
}
}
<form [formGroup]="form">
<app-address-form formControlName="mainAddress"></app-address-form>
<hr />
<ng-container formArrayName="addresses">
<ng-container *ngFor="let addressForm of addresses.controls; index as i">
<app-address-form [formControlName]="i"></app-address-form>
<button (click)="removeAddress(i)">Remove Address</button>
</ng-container>
</ng-container>
</form>
<button (click)="addAddress()">Add Address</button>
Child Address TS
@Component({
selector: 'app-address-form',
templateUrl: './address-form.component.html',
styleUrls: ['./address-form.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: AddressFormComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: AddressFormComponent,
},
],
})
export class AddressFormComponent
implements ControlValueAccessor, OnDestroy, Validator
{
@Input()
legend: string;
form: FormGroup = this.fb.group({
addressLine1: [null, [Validators.required]],
addressLine2: [null, [Validators.required]],
zipCode: [null, [Validators.required]],
city: [null, [Validators.required]],
});
onTouched: Function = () => {};
onChangeSubs: Subscription[] = [];
constructor(private fb: FormBuilder) {}
ngOnDestroy() {
for (let sub of this.onChangeSubs) {
sub.unsubscribe();
}
}
registerOnChange(onChange: any) {
const sub = this.form.valueChanges.subscribe(onChange);
this.onChangeSubs.push(sub);
}
registerOnTouched(onTouched: Function) {
this.onTouched = onTouched;
}
setDisabledState(disabled: boolean) {
if (disabled) {
this.form.disable();
} else {
this.form.enable();
}
}
writeValue(value: any) {
if (value) {
console.log(value);
this.form.setValue(value, { emitEvent: false });
}
}
validate(control: AbstractControl) {
if (this.form.valid) {
return null;
}
let errors: any = {};
errors = this.addControlErrors(errors, 'addressLine1');
errors = this.addControlErrors(errors, 'addressLine2');
errors = this.addControlErrors(errors, 'zipCode');
errors = this.addControlErrors(errors, 'city');
return errors;
}
addControlErrors(allErrors: any, controlName: string) {
const errors = { ...allErrors };
const controlErrors = this.form.controls[controlName].errors;
if (controlErrors) {
errors[controlName] = controlErrors;
}
return errors;
}
}
Child Address HTML
<fieldset [formGroup]="form">
<mat-form-field>
<input
matInput
placeholder="Address Line 1"
formControlName="addressLine1"
(blur)="onTouched()"
/>
</mat-form-field>
<mat-form-field>
<input
matInput
placeholder="Address Line 2"
formControlName="addressLine2"
(blur)="onTouched()"
/>
</mat-form-field>
<mat-form-field>
<input
matInput
placeholder="Zip Code"
formControlName="zipCode"
(blur)="onTouched()"
/>
</mat-form-field>
<mat-form-field>
<input
matInput
placeholder="City"
formControlName="city"
(blur)="onTouched()"
/>
</mat-form-field>
</fieldset>
Error i get is :
Error: NG01002: Must supply a value for form control with name: 'addressLine1'
Please keep in mind i am trying to avoid duplication of code by separating out as much as i can (DRY principle).
Appreciate any feedback
CodePudding user response:
This has a similar solution here.
If you're using the setValue()
method on a FormGroup
you will need to update all of the controls within that group. If you're only updating a single control, you need to use patchValue()
instead. Otherwise you will get the NG01002 error.
Change this:
writeValue(value: any) {
if (value) {
console.log(value);
this.form.patchValue(value, { emitEvent: false });
}
}
To this, adding the control you want to update as parameter:
writeValue(controlToUpdate, value: any) {
if (value) {
console.log(value);
this.form.patchValue({controlToUpdate: value},{emitEvent: false});
}
}
Hope this helps.
CodePudding user response:
You has an array of FormControls (*) (each FormControl "store" a complex object with properties: addressLine1,addressLine2,zipCode and city, but the FormArray is still a FormArray of FormControls)
So, your function addAddress should be like
addAddress() {
this.addresses.push(this.fb.control(null))
}
(*)Exists two approach when we work with nested forms. One is your approach: create a custom formControl that store a complex object and the "sub-formGroup" is created inside the component. The another is create the "sub-formGroup" in main component and pass the formGroup to a component (in this last case you not create a custom form control that implements ControlValueAccessor else a simple component with an @Input