2

I have successfully set up a Formarray that has two controls. The controls are set in the HTML form as select objects. I can successfully push and display multiple element sets but when I try to change the value of the select object in the first element of the Formarray the other Formarray element select objects take on the same value. I am using [(ngModel)] to bind a value to the object and I believe that is why the vales are always the same. I tried using an array variable with [(ngModel)]=stringArray[i] using the Formarray index, but I get errors when the page is loaded.

Can anyone suggest how to get the value of the select object using [(ngModel)] or other mechanism? I am using an ng-lightning select component (version 1.3.0) and SLDS Lightning Design CSS. I am using Angular version 4.1.3. In other parts of my app, I need to use [(ngModel)] with a string in order to get the value of a select object. The select values are an array of objects and not a primitive variable like a string. The component does not have an onSelect() function defined.

HTML:

<form [formGroup]="callFlowForm">
  <div formArrayName="callDevices">
    <!-- Check the correct way to iterate your form array -->
    <div *ngFor="let callDevice of callFlowForm.controls.callDevices.controls; let i=index"  [formGroupName]="i">
      <div class="form-group">
        <div class="slds-form-element slds-size--8-of-8">
          <ngl-form-element label="Device #: {{ i + 1 }}" class="slds-m-top--small">
            <select nglFormControl class="slds-select"
                    formControlName="callAsset"
                    [(ngModel)]="selectedDevice"
                    ngDefaultControl >
                <option *ngFor="let device of devices"
                    [value]="device.id">{{device.assetName}}
                </option>
            </select>
          </ngl-form-element>
        </div>
      </div>
      <div class="form-group">
        <div class="slds-form-element slds-size--8-of-8">
          <ngl-form-element label="Protocol:" class="slds-m-top--small">
            <select nglFormControl class="slds-select"
                    formControlName="assetTransport"
                    [(ngModel)]="selectedTransport"
                    ngDefaultControl >
                <option *ngFor="let protocol of transports"
                    [value]="protocol.id">{{protocol.type}}
                </option>
            </select>
          </ngl-form-element>
        </div>
      </div>
      <button *ngIf="callFlowForm.controls.callDevices.controls.length > 1" (click)="deleteDevice(i)" class="btn btn-danger">Delete Button</button>
    </div>
  </div>
  <button type="button" (click)="addDevice()" class="btn btn-primary">Add Device</button>
</form>

Component:

selectedTransport: string;
selectedDevice: string;
public callFlowForm: FormGroup;

transports: SipTransport[] = [{'id': 1, 'type': 'UDP'},
                              {'id': 2, 'type': 'TCP'},
                              {'id': 3, 'type': 'TLS'}
                             ];
devices: Asset[] = [{'id': 1, 'assetName': 'VCS', 'type': 'SIP Registrar'},
                    {'id': 1, 'assetName': 'CUCM', 'type': 'Call Control'},
                    {'id': 1, 'assetName': 'SBC', 'type': 'Call Control'},
                    {'id': 1, 'assetName': 'EX60', 'type': 'Endpoint'}
                   ];

constructor(private dialService: DialService,
    private formBuilder: FormBuilder
) { }

ngOnInit() {
    this.selectedDevice = '1';
    this.selectedTransport = '1';
    this.callFlowForm = this.formBuilder.group({
      callDevices: this.formBuilder.array([this.addDevices()]) // here
    });

}

addDevices() {
      return this.formBuilder.group({
          callAsset: [''],
          assetTransport: ['']
      });
}

addDevice() { 
  const control = <FormArray>this.callFlowForm.controls['callDevices'];
  control.push(this.addDevices());
}

deleteDevice(index: number) {
  const control = <FormArray>this.callFlowForm.controls['callDevices'];
  control.removeAt(index);
}
Tony B.
  • 91
  • 4
  • 14
  • You are absolutely correct that this is because of the ngModel. Two-way-binding is ***highly*** discouraged in reactive forms. I'm having trouble understanding why you need two-way-binding here. Why not use the formcontrol instead. That's what should be used :) – AT82 Jun 01 '17 at 08:57
  • Hi @AJT_82, I don't need ngModel, but I have used it in other forms especially in edit forms where I want to pre-populate the field with current value to be edited. Your suggestion works as I was able to console.log the values selected without using ngModel. I just need to figure out how to iterate through the formarray data in the component when submitted. I got this in the console: This is my form {"callDevices":[{"callAsset":"24","assetTransport":"2"},{"callAsset":"34","assetTransport":"1"}]} – Tony B. Jun 01 '17 at 23:18
  • You can also set preselected values with the form control, either by not building form before values have been set. Or then by a patching form with values when you have received them... if this is async. But on to your question... *I just need to figure out how to iterate through the formarray data in the component when submitted* Why do you need to iterate it? I mean what do you want to accomplish with the iterating? And as a side note it's just like iterating any array :) – AT82 Jun 02 '17 at 07:13

2 Answers2

0

Why don't you do this?

Component class:

selectedDevice: Asset;

HTML code:

           <select nglFormControl class="slds-select"
                formControlName="callAsset"
                [(ngModel)]="selectedDevice"
                ngDefaultControl >
            <option *ngFor="let device of devices"
                [value]="device">{{device.assetName}}
            </option>
        </select>`
Thiagz
  • 121
  • 1
  • 13
  • Your suggestion didn't work. Any version of ngModel seems to just tie the select objects together. Removing ngModel seems to be the right path to take, I just need to figure out how to iterate through the formarray values now. Thanks. – Tony B. Jun 01 '17 at 23:14
0

Thank you to @AJT_82 who helped get me going in the right direction. The question may not have been worded the best as the answer is you DON'T use ngModel in a formarray. You need to set and retrieve the values via the form controls. This has confused me when the examples on the Internet try to explain the difference between "template driven" versus "model driven" or "reactive" forms. They always seemed to be just the same.

This is how the HTML should be.

HTML:

<select nglFormControl class="slds-select"
        formControlName="callAsset"
        ngDefaultControl >
        <option *ngFor="let device of devices"
           [value]="device.id">{{device.assetName}}
        </option>
</select>

In my app I am using the array values to search through a larger collection. When I want to get the values of the form I implement this:

Component:

const formValue1 = this.callFlowForm.value;

// this allows me to iterate through all devices in the formarray
// and push those values into a list for sorting and further processing
formValue1.callDevices.forEach((element: any) => {
    allDevices.push(element.callDevice);
});

Not in this example, but in another form I needed to set the values of the form controls. I used form.setValue({}) where you need to set the value of all the form controls. Then if you need to just edit a single value then you use form.patchValue({}). An example of a set in that other form is this.

setValue:

this.dialForm.setValue({
   id: this.editDialplan.id,
   number: this.editDialplan.number,
   domainid: this.editDialplan.domainid.id
});

patchValue:

this.dialForm.patchValue({
   number: this.editDialplan.number,
});
Tony B.
  • 91
  • 4
  • 14