langen
langen

Reputation: 770

Nested form components are not set with initial values on submit

I am trying to implement nested form components in angular with reactive forms using ControlValueAccessors, and I have been following this guide: https://blog.angularindepth.com/angular-nested-reactive-forms-using-cvas-b394ba2e5d0d

I have got everything to work except one major issue. If my form is pre-populated, i.e. the form is used to edit existing data instead of creating, then the initial data is not set on the form's value upon submit if the corresponding child component form values hasn't been changed.

I have an example setup on the following link (Open up the console in the down right corner): https://stackblitz.com/edit/angular-nested-forms-cva-izbrht

As you can see, if you press submit immediately, the form value contains two properties which value is just two empty strings. If you edit e.g. the first name and then submit you will see that the basicInfo property now has been properly set with its values.

Upvotes: 3

Views: 2878

Answers (5)

clandyuki
clandyuki

Reputation: 11

here is my solution,

change registerOnChange to this:

registerOnChange(fn) {
  this.onChange = fn;
}

then call the onChange method in your child component ngOnInit hook like this:

 setTimeout(() => {
  this.onChange(this.form.value);
});

done! notice using setTimeout wrap, otherwise it won't work.

Upvotes: 1

Suchin
Suchin

Reputation: 421

Default values can be Passed to nested form when creating the parent form itself. This will resolve the issuethe form is used to edit existing data instead of creating, then the initial data is not set on the form's value upon submit if the corresponding child component form values hasn't been changed.

this.nestedForm= new FormGroup({
  basicInfo: defaultValues ? new FormControl(defaultValues.basicInfo) : new FormControl(),
  address: defaultValues ? new FormControl(defaultValues.address) : new FormControl()
});

So here is the defaultValues object to set default values for example in https://blog.angularindepth.com/angular-nested-reactive-forms-using-cvas-b394ba2e5d0d

const defaultValues = {
  basicInfo: {
    fname: 'suchin',
    lname: 'b'
  },
  address: {
    addressLine: 'Home',
    areacode: 'Test Code'
  }
}

Upvotes: 0

Eliseo
Eliseo

Reputation: 58124

@langen, for me it's use [formGroup] and [formControl] . it's a coment of this question in stackoverflow and the stackblitz

My Main

<form [formGroup]="formGroup">
  <app-personal-details-form [formGroup]="formGroup"></app-personal-details-form>
</form>
//It's in main where I create the formGroup:
formGroup = new FormGroup(
    {
      name: new FormGroup({
        firstname: new FormControl(null, { validators: [Validators.required] }),
        lastname: new FormControl(null, { validators: [Validators.required] }),
      }),
      gender: new FormControl(null, { validators: [Validators.required] }),
      address: new FormGroup({
        streetaddress: new FormControl(null, { validators: [Validators.required] }),
        city: new FormControl(null, { validators: [Validators.required] }),
        state: new FormControl(null, { validators: [Validators.required] }),
        zip: new FormControl(null, { validators: [Validators.required] }),
        country: new FormControl(null, { validators: [Validators.required] })
      }),
      phone: new FormGroup({
        phone: new FormControl(null, { validators: [Validators.required] }),
        countrycode: new FormControl(null, { validators: [Validators.required] }),
      })

    })

The rest of component:

Personal-details

<div [formGroup]="formGroup">
    <app-name-form [formGroup]="formGroup.get('name')"></app-name-form>
    <select formControlName="gender">
    <option *ngFor="let gender of Gender | keyvalue" [value]="gender.value">{{ gender.value }}</option>
  </select>
  <app-address-form [formGroup]="formGroup.get('address')"></app-address-form>
  <app-phone-form [formGroup]="formGroup.get('phone')"></app-phone-form>
</div>
//and 
@Input() formGroup:FormGroup;

Address form

<div [formGroup]="formGroup">
  <input type="text" placeholder="Street address" formControlName="streetaddress">
  <span *ngIf="formGroup.get('streetaddress').invalid">*</span>
  <input type="text" placeholder="City" formControlName="city">
  <input type="text" placeholder="State" formControlName="state">
  <input type="text" placeholder="Zip" formControlName="zip">
  <input type="text" placeholder="Country" formControlName="country">
</div>
//and 
@Input() formGroup;

Name-form

<div [formGroup]="formGroup">
  <input type="text" placeholder="First name" formControlName="firstname">
  <input type="text" placeholder="Last name" formControlName="lastname">
</div>
//and
    @Input() formGroup;

etc.

Upvotes: 1

Eliseo
Eliseo

Reputation: 58124

@Lagen, when you has a custom form control you need feed in the component.e.g. in your billing-info.component.

public nestedForm: FormGroup = new FormGroup({
  basicInfo: new FormControl({fname:"Carl init",email:"myemail [email protected]"}),
  address: new FormControl({addressLine:"My address",areacode:'12345 new'})
  });

Off course you can remove the initialize values in component.

Yes, a FormControl can be store an object. you can think a customForm control as a "black box". It's interesting the value you feed and the value you get -not how it's made- (Personally, I think that using a custom form control to create a nested form is to use a sledgehammer to crack nuts)

Upvotes: 0

langen
langen

Reputation: 770

I solved it by creating the complete FormGroup in the parent component, and then passing in the child forms to the child components as inputs. This removes the need to use ControlValueAccessors.

I am not happy with this solution because I would like the child components to have the responsibility to create their formgroup, but I can't find any solution to my problem, so this will have to do for now.

Upvotes: 1

Related Questions