0
votes

Dynamically changing reactive forms Controls form FormControl to FormArray.

Reactive form group:

profileForm = this.fb.group({
    name: [''],
    mobile: ['']
});

OR

profileForm = this.fb.group({
  name: [''],
  mobile: this.fb.array([
    this.fb.control('')
  ])
});

Rendering profileForm using formConfig Map:

formConfigMap = new Map([
  [
    'client',
    [{key: 'name', multi: false}, {key: 'mobile', multi: false}],
  ],
  [
    'customer',
    [{key: 'name', multi: false}, {key: 'mobile', multi: true}],
  ],
]); 

HTML:

<form [formGroup]="profileForm">
  <div *ngFor="let field of formConfigMap.get('client' OR 'customer')">
    <div *ngIf="field.multi; else singleControl" formArrayName="mobile">
      <div *ngFor="let control of mobile.controls; let i=index">
        <input type="text" [formControlName]="i">
      </div>
    </div>
    <ng-template #singleControl>
      <input type="text" [formControlName]="field.key">
    </ng-template>
  </div>
</form>

Whenever I am changing profileForm object form 'client' to 'customer', I am getting "TypeError: control.registerOnChange is not a function" Error. because of dynamically changing in reactive forms Control(mobile: FormControl -> FormArray).

If I am changing profileForm object from 'customer' to 'client', It works perfectly fine (mobile: FormArray -> FormControl).

Any suggestion will help.

I have tried "removeControl(name: string)" method to remove control(mobile: FormControl) first and then "addControl(name, control)" method to add control(mobile as FormArray).

2

2 Answers

2
votes

If you have reactive forms and an array control in reactive form change formControlName to formArrayName

component.ts:

 this.taskForm = this._formBuilder.group({
  RepeatTypeID: ['', null],
  SelectedWeekDays: new FormArray([]),
});

component.html:

  <mat-checkbox formArrayName="SelectedWeekDays" >{{wdItems.Weekdayname}}</mat-checkbox>
0
votes

Really I don't like that the template in inside the loop, I sugested a change that you can see in this stackblitz I use *ngTemplateOutlet and pass as context the field and the control:

<form *ngIf="profileForm" [formGroup]="profileForm">
    <div *ngFor="let field of formConfigMap.get(key)">
        <div *ngTemplateOutlet="field.multi?arrayControl:singleControl;
                context: {$implicit:field,control:profileForm.get(field.key)}">
        </div>
    </div>
</form>

<ng-template #singleControl let-field let-control="control">
    <div>
        <input type="text" [formControl]="control">
  </div>
</ng-template>
<ng-template #arrayControl let-field  let-control="control">
    <div [formGroup]="control">
        <div *ngFor="let c of control.controls;let i=index">
            <input type="text" [formControl]="c">
        </div>
        </div>
</ng-template>

See that to mannage the array I use [formGroup] and use [formControl] directly

NOTE: I pass the "field" as implicit

Update bonus, if we want a template for a FormGroup it's can be like

<ng-template #groupControl let-field  let-control="control">
    <div [formGroup]="control">
        <div *ngFor="let c of control.controls|keyvalue;let i=index">
            <input type="text" [formControl]="c.value">
        </div>
     </div>
</ng-template>