0

I am attempting to populate a select form field from an HTTP call. I get the data but cannot seem to set the value correctly utilizing a FormArray.

I have tried adjusting my object calls.references and even utilized array notation. I can get it to dump to the console, but not populate the select form field. So I am thinking that something is incorrect in my Angular code, but I have been unsuccessful in finding an example of this online. All of the documentation/tutorials I have come across do not have selects in the form.

public ngOnInit () {
    // Reactive form fields
    this.timecardForm = this.fb.group( {
      payBeginningDate: [ '2019-03-19', [ Validators.required ] ],
      payEndingDate: [ '2019-03-26', [ Validators.required ] ],
      payCategoriesTracked: this.fb.array( [ this.buildPayCategoriesTracked() ] ), // This is the one on which I am working
      overtimeCategories: '1',
      workHoursTracked: this.fb.array( [ this.buildWorkTracked() ] ),
      earnings: [ { value: null, disabled: true }, [ Validators.required ] ],
      totalHours: [ { value: null, disabled: true }, [ Validators.required ] ],
      totalEarnings: [ { value: null, disabled: true }, [ Validators.required ] ]
    } );
... // There is some more unrelated code
// Dynamically build payCategories row
  buildPayCategoriesTracked (): FormGroup {
    console.log( 'buildPayCategoriesTracked: ', this.timeEntry.payCategories ); // This prints to the console successfully
    return this.fb.group( {
      payCategories: [ this.timeEntry.payCategories, [ Validators.required ] ]
    } );
  }
<!-- The HTML in question... -->
<select formControlName="{{i}}"
        id="{{ 'payCategoriesTracked' + i }}"
        class="form-control"
        (change)="onRecordUpdated(timeCard, timecardDet)"
        [ngClass]="{'is-invalid': payCategoriesMessage }">
        <option value=null
                disabled
                selected
                hidden>--Select--</option>
        <option *ngFor="let payCategory of payCategories"
        [ngValue]="payCategoryDescription.payCategoryId">{{payCategoryDescription}}</option>
        </select>

I simply want my payCategories to populate my select form field.An example of one of the items returned is:

{payCategoryId: 9, description: "DUE FROM LIBRAR", payType: "Hourly", isActive: true}

So I want the select value to be the id and the description to display in the options tag.

Update I have changed my HTML as follows...

<div *ngFor="let payCategoriesTracked of payCategoriesTracked.controls;let i=index">
<mat-form-field>
    <mat-label>Pay Category</mat-label>
    <mat-select formControlName="i"
                (change)="onRecordUpdated(timeCard, timecardDet)"
                [ngClass]="{'is-invalid': payCategoriesMessage }">
        <mat-option *ngFor="let payCategory of payCategories"
                    [ngValue]="payCategory.value">
            {{payCategory.description}}
        </mat-option>
    </mat-select>
</mat-form-field>

Solution I figured it out...woot! Here is my solution...

<mat-form-field>
    <mat-label>Pay Category</mat-label>
    <select matNativeControl
            formControlName="payCategory">
        <option *ngFor="let payCategory of payCategories"
                [ngValue]="payCategory">
          {{payCategory.description}}
        </option>
    </select>
</mat-form-field>
// This is the FormControl, which is within a formgroup...
payCategory: new FormControl( this.buildPayCategories() )
// Pull in payCategories for select list from service
  buildPayCategories () {
    this.payCategories = this.timeEntry.payCategories;
    return this.payCategories;
  }
4
  • Can I see the code for your <select>? As well as the data that is binded to it? Commented Apr 15, 2019 at 17:05
  • Done...sorry about that. Commented Apr 15, 2019 at 17:09
  • Why *ngFor="let payCategory of payCategories" in the template and not *ngFor="let payCategory of timeEntry.payCategories", as used in the component? Commented Apr 15, 2019 at 17:17
  • Also...if anyone is aware of a resource that can help guide me, I would appreciate it. Commented Apr 15, 2019 at 17:40

2 Answers 2

1

Use the 'this.timeEntry.payCategories' in the template to build the options, using ngFor

<option *ngFor="let payCategory of timeEntry.payCategories" [value]="payCategory.payCategoryId">{{ payCategory.description }}</option>

When building the Reactive Form, the first parameter in the FormControl should be the value from the select, in other words, the selected option. Example:

return this.fb.group( {
      payCategories: [ 2, [ Validators.required ] ]
    } );

Will match the option with value equals 2.

Sign up to request clarification or add additional context in comments.

1 Comment

I made your suggested change and it did not fix it. Could it be because I have it in the following div? <div formArrayName="payCategoriesTracked" *ngFor="let payCategoriesTracked of payCategoriesTracked.controls; let i-index"><div [formGroupName]="i">...
0

Clay, you need choose if you want a FormArray of FormGroup or a FormArray of FormControls.

A FormArray of FormGroups

  myForm=new FormGroup({
    myArray:new FormArray([
    new FormGroup({
      prop1:new FormControl('',Validators.required),
      prop2:new FormControl('',Validators.required)
    })
  ])
  })

<!--your form--->
<form [formGroup]="myForm">
    <!--a div with formArrayName--->
    <div formArrayName="myArray">
        <!--a div that iterate over "controls" and has [formGroupName]-->
        <div *ngFor="let group of myForm.get('myArray').controls;
                  let i=index" [formGroupName]="i">
            <!--your input using formControlName-->
            <input formControlName="prop1">
            <input formControlName="prop2"/>
         </div>
    </div>
</form>
{{myForm?.value|json}}
//Some like { "myArray": [ { "prop1": "", "prop2": "" } ] } 

A FormArray of FormControls

  myForm2 = new FormGroup({
    myArray: new FormArray([
      new FormControl('', Validators.required),
      new FormControl('', Validators.required)
    ])
  })

<!--your form--->
<form [formGroup]="myForm2">
    <!--a div with formArrayName--->
  <div formArrayName="myArray">
  <!--a div that iterate over "controls"-->
  <div *ngFor="let group of myForm2.get('myArray').controls;let i=index">
      <!--a unique element using [formControlName]-->
      <input [formControlName]="i">
  </div>
  </div>
</form>
{{myForm2?.value|json}}
//will be like  { "myArray": [ "", "" ] }

NOTE: I use directy new FormGroup and new FormArray , not FormBuilder. This avoid the need of inject the FormBuilder and, if you want, change the "change detection", but in sintax it's not much the difference

NOTE2: there are more differents ways to refered to a control in an array but I think this are the more clerest.

TIPs for make a form

  1. Always beging with {{form?.value|json}}. A formGroup exist independlty of the "inputs"
  2. we beging write <form *ngIf="form" [formGroup]="myForm"></form>
  3. At first use simple inputs
  4. Always need know what we want to get in form.value

Update About select. A select it's a input, only need give the formControlName to the "select". Well a select need an array of objects to show the options. If our array of object has two properties, "value" and "text" generally we use some like:

//If our arrayForm is an ArrayForm of FormGroups
<select formControlName="prop1">
  <options *ngFor="let item of options" [value]="item.value">
      {{item.text}}
  </option> 
</select>
//If our arrayForm is a Array of FormControls
<select [formControlName]="i">
  <options *ngFor="let item of options" [value]="item.value">
      {{item.text}}
  </option> 
</select>

First, take account there NO [selected] anywhere: it's not necesary. If we can selected a value we'll give value to the FormControl.

When we are using ReactiveForm, usually we subscribe to changeValue propertie of the control in spite of use (change), and we must be carefully: In options we can use [value] or [ngValue]. but the value can be a simple variable (a string or a number) or a complex object. If we are not using dropdop in cascate, the normal it's use only a simple variable.

Update I made changes and I think I am close because I see the data dumped to the screen via {{timecardForm?.value|json}}

I am receiving the control.registerOnChange is not a function error, which leads me to believe I have something referenced incorrectly. Here is my updated code...

<div class="col-2">
          <div formArrayName="payCategoriesTracked">
            <div *ngFor="let payCategory of payCategoriesTracked.controls; let i=index">
              <mat-form-field>
                <mat-label>Pay Category</mat-label>
                <mat-select [formControlName]="i"
                            (change)="onRecordUpdated(timeCard, timecardDet)"
                            [ngClass]="{'is-invalid': payCategoriesMessage }">
                  <mat-option *ngFor="let payCategory of payCategories"
                              [ngValue]="payCategory">
                    {{payCategory.description}}
                  </mat-option>
                </mat-select>
              </mat-form-field>
            </div>
          </div>

The data dump is as follows...

"payCategoriesTracked": [ { "payCategories": [ { "payCategoryId": 1, "description": "Converted Data ",...etc.

4 Comments

Thank you for the information. Can you alter your code to include a select field please? I have the regular input fields figured out. I cannot get the select form field to work.
I updated the answer. A select it's not more than an input. See that in your code, you're using the .html like your formArray is a FormArray of FormControls, but you're defining in the .ts as a FormArray of FormGroups. This is the reason I want to show the two options
Thank you for the info. It is beginning to come clearer. I made some adjustment and am receiving the error of "Cannot find control with name: 'i'". I am using Angular Material and wrapped it in <div *ngFor="let payCategoriesTracked of payCategoriesTracked.controls;let i=index">. I changed the formControlName to "i" and added *ngFor="let payCategory of payCategories" in the mat-option. What are your thoughts on why I am receiving this error. Is it because I have an array inside an array?
Just some more info...when I dump the control to the console, the value of the FormArray is populated. It is an array with a length of 1. That first slot has an object, which contains 81 entries, in it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.