17
votes

I have a child component which deals with the array of input controls. I want to have a formcontrol over the child component.

I am passing the array of json object, what would be the correct way to bind parent form to the child component's FormArray having 2 form control with Validator required on first.

This is the initial code

<h1>Child</h1>
<div formArrayName="names">
 <div *ngFor="let c of names.control">
  <input formControlName="firstName">
  <input formControlName="lastName">
 </div>
</div>

Intention is to bind parent form with the array of input control in the child component. Also form will become invalid if one of the input control in child component doesn't have required field.

http://plnkr.co/edit/HznCJfSEiSV28ERqNiWr?p=preview

2
Could you clarify what your intent is with setting the username form control to a json object? Are you attempting to list, in the child component, a first name and last name for each firstname lastname pair in the json? - silentsod
Yes, with the given json, I am trying to render each pair in the input control. - Anonymous Me
@AnonymousMe, have you find any solution on this problem ? - Junaid
It is easy to do so with ngx-sub-form see github.com/cloudnc/ngx-sub-form#dealing-with-arrays - maxime1992

2 Answers

5
votes

I love solve old post :)

The key is that your custom Form Component has inside a FormArray, then use "writeValue" to create the formArray, see stackblitz

@Component({
    selector: "my-child",
    template: `
    <h1>Child</h1>

    <div *ngFor="let group of formArray.controls" [formGroup]="group">
        <input formControlName="firstName" (blur)="_onTouched()" />
        <input formControlName="lastName" (blur)="_onTouched()"/>
    </div>
    `,
    providers: [{
            provide: NG_VALUE_ACCESSOR,
            useExisting: Child,
            multi: true
        },
        {
            provide: NG_VALIDATORS,
            useExisting: Child,
            multi: true
        }
    ]
})
export class Child implements ControlValueAccessor {
    formArray: FormArray;
    _onChange;
    _onTouched;

    writeValue(value: any) {
        this.formArray = new FormArray(
            value.map(x => {
                return new FormGroup({
                    firstName: new FormControl(x.firstName, Validators.required),
                    lastName: new FormControl(x.firstName, Validators.required)
                });
            })
        );
        this.formArray.valueChanges.subscribe(res => {
            this._onChange(res);
        });
    }

    registerOnChange(fn: (value: any) => void) {
        this._onChange = fn;
    }

    registerOnTouched(fn: (value: any) => void) {
        this._onTouched = fn;
    }

    validate({ value }: FormControl) {
        return !this.formArray || this.formArray.valid ?
            null : { error: "Some fields are not fullfilled" };
    }
}
1
votes

You have to use formArrayName directive and *ngFor like this:

<form [formGroup]="form" (ngSubmit)="sayHello()">
       <input formControlName="name"><br>
       <input formControlName="email"><br>
       <div formArrayName="username">
           <div *ngFor="let user of username.controls; let i=index">
                <my-child formControlName="i"></my-child>
           </div>
       </div>
       <button type="submit">Register</button>
</form>

And with FormBuilder you have to use FormArray as well.

form = new FormGroup({
     name: new FormControl('My Name'),
     username: new FormArray([
                  new FormControl("value"),// ControlValueAccesor is applied only to one control, not two. So you cannot use javascript object like you are using below this line.
                  {firstName:"Anna", lastName:"Smith"},
                  {firstName:"Peter", lastName:"Jones"}
                ])
   });

For more details, see this doc.

Case 2: passing FormGroup:

form = new FormGroup({
     name: new FormControl('My Name'),
     username: new FormArray([
                  new FormGroup({
                        firstName: new FormControl('Anna'),
                        lastName: new FormControl('Smith')
                  }),
                  new FormGroup({
                        firstName: new FormControl('Peper'),
                        lastName: new FormControl('Jones')
                  }),
                ])
   })

If you are tring to pass the FormGroup as a ngModel parameters, you can't!